| /* |
| * Copyright 2007, The Android Open Source Project |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #define LOG_TAG "webviewglue" |
| |
| #include <config.h> |
| |
| #include "android_graphics.h" |
| #include "AndroidLog.h" |
| #include "AtomicString.h" |
| #include "CachedFrame.h" |
| #include "CachedNode.h" |
| #include "CachedRoot.h" |
| #include "FindCanvas.h" |
| #include "Frame.h" |
| #include "GraphicsJNI.h" |
| #include "IntPoint.h" |
| #include "IntRect.h" |
| #include "Node.h" |
| #include "PlatformGraphicsContext.h" |
| #include "PlatformString.h" |
| #include "SelectText.h" |
| #include "SkBlurMaskFilter.h" |
| #include "SkCanvas.h" |
| #include "SkCornerPathEffect.h" |
| #include "SkDumpCanvas.h" |
| #include "SkPath.h" |
| #include "SkPicture.h" |
| #include "SkPixelXorXfermode.h" |
| #include "SkRect.h" |
| #include "SkTime.h" |
| #include "WebCoreJni.h" |
| #include "WebViewCore.h" |
| #include "jni_utility.h" |
| |
| #ifdef ANDROID_INSTRUMENT |
| #include "TimeCounter.h" |
| #endif |
| |
| #ifdef GET_NATIVE_VIEW |
| #undef GET_NATIVE_VIEW |
| #endif |
| |
| #define GET_NATIVE_VIEW(env, obj) ((WebView*)env->GetIntField(obj, gWebViewField)) |
| |
| #include <ui/KeycodeLabels.h> |
| #include <JNIHelp.h> |
| #include <jni.h> |
| |
| #define REPLAY_BUFFER_SIZE 4096 |
| |
| namespace android { |
| |
| struct CommonParams { |
| enum Trigger { |
| NoData, |
| ClearFocusParams, |
| FirstMoveFocusParams, |
| MoveFocusParams, |
| MotionUpParams |
| } m_trigger; |
| int m_generation; |
| }; |
| |
| struct CacheParams { |
| void setFocus(const CachedNode* node, |
| const CachedFrame* frame, const CachedRoot* root, |
| const WebCore::IntPoint& focusLocation) |
| { |
| m_node = (WebCore::Node*) (node ? node->nodePointer() : 0); |
| m_frame = (WebCore::Frame*) (node ? frame->framePointer() : 0); |
| m_x = focusLocation.x(); |
| m_y = focusLocation.y(); |
| } |
| |
| WebCore::Node* m_node; |
| WebCore::Frame* m_frame; |
| int m_x; |
| int m_y; |
| }; |
| |
| struct ClearFocusParams { |
| CommonParams d; |
| CacheParams c; |
| int m_x; |
| int m_y; |
| }; |
| |
| struct MotionUpParams { |
| CommonParams d; |
| int m_x; |
| int m_y; |
| int m_slop; |
| bool m_isClick; |
| }; |
| |
| struct FirstMoveFocusParams { |
| CommonParams d; |
| int m_keyCode; |
| int m_count; |
| bool m_ignoreScroll; |
| }; |
| |
| struct MoveFocusParams { |
| FirstMoveFocusParams d; |
| CacheParams c; |
| void* m_sentFocus; |
| WebCore::IntRect m_sentBounds; |
| WebCore::IntRect m_visibleRect; |
| CachedHistory m_history; // FIXME: make this a subset |
| int m_xMax; |
| int m_yMax; |
| }; |
| |
| typedef MoveFocusParams LargestParams; |
| |
| #if DEBUG_NAV_UI |
| static const char* TriggerNames[] = { |
| "*** no data ! ***", |
| "clearFocus", |
| "firstMoveFocus", |
| "moveFocus", |
| "motionUp" |
| }; |
| #endif |
| |
| class FocusReplay { |
| public: |
| FocusReplay() : m_start(m_buffer), m_end(m_buffer), m_lastGeneration(0) |
| { |
| } |
| |
| // find the most recent common data |
| void add(const CommonParams& data, size_t len) |
| { |
| DBG_NAV_LOGD("m_start=%d m_end=%d trigger=%s moveGeneration=%d", m_start - m_buffer, |
| m_end - m_buffer, TriggerNames[data.m_trigger], data.m_generation); |
| m_lastGeneration = data.m_generation; |
| char* limit = m_buffer + sizeof(m_buffer); |
| int used = m_end - m_start; |
| if (used < 0) |
| used += sizeof(m_buffer); |
| int needed = (int) len - ((int) sizeof(m_buffer) - used); |
| if (needed >= 0) |
| reclaim(++needed); |
| if (m_end + len <= limit) { |
| memcpy(m_end, (void*) &data, len); |
| m_end += len; |
| DBG_NAV_LOGD("m_start=%d m_end=%d", m_start - m_buffer, m_end - m_buffer); |
| return; |
| } |
| size_t partial = limit - m_end; |
| memcpy(m_end, (void*) &data, partial); |
| const void* remainder = (const void*) ((const char*) &data + partial); |
| partial = len - partial; |
| memcpy(m_buffer, remainder, partial); |
| m_end = m_buffer + partial; |
| DBG_NAV_LOGD("wrap m_start=%d m_end=%d", |
| m_start - m_buffer, m_end - m_buffer); |
| } |
| |
| int count() |
| { |
| DBG_NAV_LOGD("m_start=%d m_end=%d", |
| m_start - m_buffer, m_end - m_buffer); |
| if (m_start == m_end) |
| return 0; |
| char* limit = m_buffer + sizeof(m_buffer); |
| char* saveStart = m_start; |
| int result = 0; |
| while (true) { |
| ++result; |
| m_start += triggerSize(); |
| if (m_start == m_end) |
| break; |
| if (m_start < limit) |
| continue; |
| m_start -= sizeof(m_buffer); |
| if (m_start == m_end) |
| break; |
| } |
| m_start = saveStart; |
| DBG_NAV_LOGD("count=%d", result); |
| return result; |
| } |
| |
| void discard(int generation) |
| { |
| DBG_NAV_LOGD("generation=%d", generation); |
| LargestParams storage; |
| const CommonParams& params = storage.d.d; |
| char* pos = position(); |
| retrieve(&storage.d.d); |
| if (params.m_generation > generation) { |
| DBG_NAV_LOGD("params.m_generation=%d > generation=%d", |
| params.m_generation, generation); |
| rewind(pos); |
| DBG_NAV_LOGD("m_start=%d m_end=%d", m_start - m_buffer, m_end - m_buffer); |
| return; |
| } |
| LOG_ASSERT(params.m_generation == generation, "params.m_generation != generation"); |
| DBG_NAV_LOGD("m_start=%d m_end=%d", m_start - m_buffer, m_end - m_buffer); |
| } |
| |
| int lastAdd() |
| { |
| return m_lastGeneration; |
| } |
| |
| char* position() |
| { |
| return m_start; |
| } |
| |
| int retrieve(CommonParams* data) |
| { |
| if (m_end == m_start) { |
| // changed from LOGD to LOGV, as it always fires when I click to center |
| // text (mrr) |
| LOGV("%s *** no data to retrieve (error condition) ***", __FUNCTION__); |
| data->m_trigger = CommonParams::NoData; |
| return data->m_generation = INT_MAX; |
| } |
| DBG_NAV_LOGD("m_start=%d m_end=%d", |
| m_start - m_buffer, m_end - m_buffer); |
| char* limit = m_buffer + sizeof(m_buffer); |
| size_t size = triggerSize(); |
| if (m_start < m_end) { |
| LOG_ASSERT((size_t) (m_end - m_start) >= size, "m_end - m_start < size"); |
| memcpy(data, m_start, size); |
| m_start += size; |
| } else { |
| int partial = limit - m_start; |
| if (partial > (int) size) |
| partial = size; |
| memcpy(data, m_start, partial); |
| m_start += partial; |
| void* remainder = (void*) ((char*) data + partial); |
| partial = size - partial; |
| if (partial > 0) { |
| memcpy(remainder, m_buffer, partial); |
| m_start = m_buffer + partial; |
| LOG_ASSERT(m_start <= m_end, "m_start > m_end"); |
| } |
| } |
| if (m_start == limit) { |
| m_start = m_buffer; |
| if (m_end == limit) |
| m_end = m_buffer; |
| } |
| DBG_NAV_LOGD("m_start=%d m_end=%d trigger=%s moveGeneration=%d", |
| m_start - m_buffer, m_end - m_buffer, TriggerNames[data->m_trigger], |
| data->m_generation); |
| return data->m_generation; |
| } |
| |
| void rewind(char* pos) |
| { |
| m_start = pos; |
| } |
| |
| private: |
| void reclaim(int needed) |
| { |
| DBG_NAV_LOGD("needed=%d", needed); |
| char* limit = m_buffer + sizeof(m_buffer); |
| do { |
| size_t size = triggerSize(); |
| m_start += size; |
| needed -= size; |
| if (m_start >= limit) { |
| m_start = m_buffer + (m_start - limit); |
| if (m_end == limit) |
| m_end = m_buffer; |
| } |
| } while (needed > 0 && m_start != m_end); |
| DBG_NAV_LOGD("m_start=%d m_end=%d", |
| m_start - m_buffer, m_end - m_buffer); |
| } |
| |
| size_t triggerSize() |
| { |
| LOG_ASSERT(m_start != m_end, "m_start == m_end"); |
| char* limit = m_buffer + sizeof(m_buffer); |
| LOG_ASSERT(m_start + sizeof(CommonParams::Trigger) <= limit, "trigger not in limit"); |
| CommonParams::Trigger trigger; |
| memcpy(&trigger, m_start, sizeof(trigger)); |
| switch (trigger) { |
| case CommonParams::ClearFocusParams: |
| return sizeof(ClearFocusParams); |
| case CommonParams::FirstMoveFocusParams: |
| return sizeof(FirstMoveFocusParams); |
| case CommonParams::MoveFocusParams: |
| return sizeof(MoveFocusParams); |
| case CommonParams::MotionUpParams: |
| return sizeof(MotionUpParams); |
| default: |
| LOG_ASSERT(0, "trigger undefined"); |
| } |
| return 0; |
| } |
| |
| char m_buffer[REPLAY_BUFFER_SIZE]; |
| char* m_start; |
| char* m_end; |
| int m_lastGeneration; |
| }; // end of helper class ReplayFocus |
| |
| static jfieldID gWebViewField; |
| |
| //------------------------------------- |
| |
| static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[]) |
| { |
| jmethodID m = env->GetMethodID(clazz, name, signature); |
| LOG_ASSERT(m, "Could not find method %s", name); |
| return m; |
| } |
| |
| //------------------------------------- |
| // This class provides JNI for making calls into native code from the UI side |
| // of the multi-threaded WebView. |
| class WebView |
| { |
| public: |
| enum FrameCachePermission { |
| DontAllowNewer, |
| AllowNewer, |
| AllowNewest |
| }; |
| |
| enum OutOfFocusFix { |
| DoNothing, |
| ClearTextEntry, |
| UpdateTextEntry |
| }; |
| |
| struct JavaGlue { |
| jobject m_obj; |
| jmethodID m_clearTextEntry; |
| jmethodID m_overrideLoading; |
| jmethodID m_scrollBy; |
| jmethodID m_sendFinalFocus; |
| jmethodID m_sendKitFocus; |
| jmethodID m_sendMotionUp; |
| jmethodID m_setFocusData; |
| jmethodID m_getScaledMaxXScroll; |
| jmethodID m_getScaledMaxYScroll; |
| jmethodID m_getVisibleRect; |
| jmethodID m_updateTextEntry; |
| jmethodID m_displaySoftKeyboard; |
| jmethodID m_viewInvalidate; |
| jmethodID m_viewInvalidateRect; |
| jmethodID m_postInvalidateDelayed; |
| jfieldID m_rectLeft; |
| jfieldID m_rectTop; |
| jmethodID m_rectWidth; |
| jmethodID m_rectHeight; |
| jfieldID m_focusNode; |
| jmethodID m_setAll; |
| AutoJObject object(JNIEnv* env) { |
| return getRealObject(env, m_obj); |
| } |
| } m_javaGlue; |
| |
| WebView(JNIEnv* env, jobject javaWebView, int viewImpl) |
| { |
| jclass clazz = env->FindClass("android/webkit/WebView"); |
| // m_javaGlue = new JavaGlue; |
| m_javaGlue.m_obj = adoptGlobalRef(env, javaWebView); |
| m_javaGlue.m_scrollBy = GetJMethod(env, clazz, "setContentScrollBy", "(IIZ)V"); |
| m_javaGlue.m_clearTextEntry = GetJMethod(env, clazz, "clearTextEntry", "()V"); |
| m_javaGlue.m_overrideLoading = GetJMethod(env, clazz, "overrideLoading", "(Ljava/lang/String;)V"); |
| m_javaGlue.m_sendFinalFocus = GetJMethod(env, clazz, "sendFinalFocus", "(IIII)V"); |
| m_javaGlue.m_sendKitFocus = GetJMethod(env, clazz, "sendKitFocus", "()V"); |
| m_javaGlue.m_sendMotionUp = GetJMethod(env, clazz, "sendMotionUp", "(IIIIIIIZZ)V"); |
| m_javaGlue.m_setFocusData = GetJMethod(env, clazz, "setFocusData", "(IIIIIIZ)V"); |
| m_javaGlue.m_getScaledMaxXScroll = GetJMethod(env, clazz, "getScaledMaxXScroll", "()I"); |
| m_javaGlue.m_getScaledMaxYScroll = GetJMethod(env, clazz, "getScaledMaxYScroll", "()I"); |
| m_javaGlue.m_getVisibleRect = GetJMethod(env, clazz, "sendOurVisibleRect", "()Landroid/graphics/Rect;"); |
| m_javaGlue.m_updateTextEntry = GetJMethod(env, clazz, "updateTextEntry", "()V"); |
| m_javaGlue.m_displaySoftKeyboard = GetJMethod(env, clazz, "displaySoftKeyboard", "()V"); |
| m_javaGlue.m_viewInvalidate = GetJMethod(env, clazz, "viewInvalidate", "()V"); |
| m_javaGlue.m_viewInvalidateRect = GetJMethod(env, clazz, "viewInvalidate", "(IIII)V"); |
| m_javaGlue.m_postInvalidateDelayed = GetJMethod(env, clazz, |
| "viewInvalidateDelayed", "(JIIII)V"); |
| jclass rectClass = env->FindClass("android/graphics/Rect"); |
| LOG_ASSERT(rectClass, "Could not find Rect class"); |
| m_javaGlue.m_rectLeft = env->GetFieldID(rectClass, "left", "I"); |
| m_javaGlue.m_rectTop = env->GetFieldID(rectClass, "top", "I"); |
| m_javaGlue.m_rectWidth = GetJMethod(env, rectClass, "width", "()I"); |
| m_javaGlue.m_rectHeight = GetJMethod(env, rectClass, "height", "()I"); |
| |
| // Set up class for updateFocusNode |
| jclass focusnodeClass = env->FindClass("android/webkit/WebView$FocusNode"); |
| LOG_ASSERT(focusnodeClass, "Could not find FocusNode class!"); |
| m_javaGlue.m_focusNode = env->GetFieldID(clazz, "mFocusNode", "Landroid/webkit/WebView$FocusNode;"); |
| m_javaGlue.m_setAll = GetJMethod(env, focusnodeClass, "setAll", "(ZZZZZIIIIIIIILjava/lang/String;Ljava/lang/String;I)V"); |
| env->DeleteLocalRef(focusnodeClass); |
| |
| env->SetIntField(javaWebView, gWebViewField, (jint)this); |
| m_viewImpl = (WebViewCore*) viewImpl; |
| m_frameCacheUI = 0; |
| m_navPictureUI = 0; |
| m_invalidNode = 0; |
| m_generation = 0; |
| m_heightCanMeasure = false; |
| m_followedLink = false; |
| m_lastDx = 0; |
| m_lastDxTime = 0; |
| m_ringAnimationEnd = 0; |
| m_selStart.setEmpty(); |
| m_selEnd.setEmpty(); |
| m_matches = 0; |
| m_hasCurrentLocation = false; |
| m_isFindPaintSetUp = false; |
| } |
| |
| ~WebView() |
| { |
| if (m_javaGlue.m_obj) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->DeleteGlobalRef(m_javaGlue.m_obj); |
| m_javaGlue.m_obj = 0; |
| } |
| delete m_frameCacheUI; |
| delete m_navPictureUI; |
| if (m_matches) |
| delete m_matches; |
| } |
| |
| void clearFocus(int x, int y, bool inval) |
| { |
| DBG_NAV_LOGD("x=%d y=%d inval=%s", x, y, |
| inval ? "true" : "false"); |
| clearTextEntry(); |
| CachedRoot* root = getFrameCache(AllowNewer); |
| if (!root) |
| return; |
| const CachedFrame* oldFrame = 0; |
| const CachedNode* oldFocusNode = root->currentFocus(&oldFrame); |
| WebCore::IntPoint focusLocation = WebCore::IntPoint(0, 0); |
| setFocusData(root->generation(), 0, 0, x, y, !oldFocusNode); |
| sendKitFocus(); |
| if (oldFocusNode) { |
| DBG_NAV_LOG("oldFocusNode"); |
| focusLocation = root->focusLocation(); |
| root->setCachedFocus(0, 0); |
| if (inval) |
| viewInvalidate(); |
| } |
| ClearFocusParams params; |
| params.d.m_trigger = CommonParams::ClearFocusParams; |
| params.d.m_generation = m_generation; |
| params.c.setFocus(oldFocusNode, oldFrame, root, focusLocation); |
| params.m_x = x; |
| params.m_y = y; |
| m_replay.add(params.d, sizeof(params)); |
| } |
| |
| void clearTextEntry() |
| { |
| DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_clearTextEntry); |
| checkException(env); |
| } |
| |
| #if DUMP_NAV_CACHE |
| void debugDump() |
| { |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (root) |
| root->mDebug.print(); |
| } |
| #endif |
| |
| // Traverse our stored array of buttons that are in our picture, and update |
| // their subpictures according to their current focus state. |
| // Called from the UI thread. This is the one place in the UI thread where we |
| // access the buttons stored in the WebCore thread. |
| // hasFocus keeps track of whether the WebView has focus && windowFocus. |
| // If not, we do not want to draw the button in a focused or pressed state |
| void nativeRecordButtons(bool hasFocus, bool pressed, bool invalidate) |
| { |
| bool focusIsButton = false; |
| const CachedNode* cachedFocus = 0; |
| // Lock the mutex, since we now share with the WebCore thread. |
| m_viewImpl->gButtonMutex.lock(); |
| if (m_viewImpl->m_buttons.size()) { |
| // Find the focused node so we can determine which node has focus, and |
| // therefore which state to paint them in. |
| // FIXME: In a future change, we should keep track of whether the focus |
| // has changed to short circuit (note that we would still need to update |
| // if we received new buttons from the WebCore thread). |
| WebCore::Node* focus = 0; |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (root) { |
| cachedFocus = root->currentFocus(); |
| if (cachedFocus) |
| focus = (WebCore::Node*) cachedFocus->nodePointer(); |
| } |
| |
| // Traverse the array, and update each button, depending on whether it |
| // is focused. |
| Container* end = m_viewImpl->m_buttons.end(); |
| for (Container* ptr = m_viewImpl->m_buttons.begin(); ptr != end; ptr++) { |
| WebCore::RenderSkinAndroid::State state; |
| if (ptr->matches(focus)) { |
| focusIsButton = true; |
| // If the WebView is out of focus/window focus, set the state to |
| // normal, but still keep track of the fact that the focus is a |
| // button |
| if (!hasFocus) { |
| state = WebCore::RenderSkinAndroid::kNormal; |
| } else if (m_followedLink || pressed) { |
| state = WebCore::RenderSkinAndroid::kPressed; |
| } else { |
| state = WebCore::RenderSkinAndroid::kFocused; |
| } |
| } else { |
| state = WebCore::RenderSkinAndroid::kNormal; |
| } |
| ptr->updateFocusState(state); |
| } |
| } |
| m_viewImpl->gButtonMutex.unlock(); |
| if (invalidate && cachedFocus && focusIsButton) { |
| const WebCore::IntRect& b = cachedFocus->getBounds(); |
| viewInvalidateRect(b.x(), b.y(), b.right(), b.bottom()); |
| } |
| } |
| |
| // These two functions separate out the particular look of the drawn find |
| // matches from the code that draws them. This function sets up the paints that |
| // are used to draw the matches. |
| void setUpFindPaint() |
| { |
| // Set up the foreground paint |
| m_findPaint.setAntiAlias(true); |
| const SkScalar roundiness = SkIntToScalar(2); |
| SkCornerPathEffect* cornerEffect = new SkCornerPathEffect(roundiness); |
| m_findPaint.setPathEffect(cornerEffect); |
| m_findPaint.setARGB(255, 132, 190, 0); |
| |
| // Set up the background blur paint. |
| m_findBlurPaint.setAntiAlias(true); |
| m_findBlurPaint.setARGB(204, 0, 0, 0); |
| m_findBlurPaint.setPathEffect(cornerEffect); |
| cornerEffect->unref(); |
| SkMaskFilter* blurFilter = SkBlurMaskFilter::Create(SK_Scalar1, |
| SkBlurMaskFilter::kNormal_BlurStyle); |
| m_findBlurPaint.setMaskFilter(blurFilter)->unref(); |
| m_isFindPaintSetUp = true; |
| } |
| |
| // Draw the match specified by region to the canvas. |
| void drawMatch(const SkRegion& region, SkCanvas* canvas, bool focused) |
| { |
| // For the match which has focus, use a filled paint. For the others, use |
| // a stroked paint. |
| if (focused) { |
| m_findPaint.setStyle(SkPaint::kFill_Style); |
| m_findBlurPaint.setStyle(SkPaint::kFill_Style); |
| } else { |
| m_findPaint.setStyle(SkPaint::kStroke_Style); |
| m_findPaint.setStrokeWidth(SK_Scalar1); |
| m_findBlurPaint.setStyle(SkPaint::kStroke_Style); |
| m_findBlurPaint.setStrokeWidth(SkIntToScalar(2)); |
| } |
| // Find the path for the current match |
| SkPath matchPath; |
| region.getBoundaryPath(&matchPath); |
| // Offset the path for a blurred shadow |
| SkPath blurPath; |
| matchPath.offset(SK_Scalar1, SkIntToScalar(2), &blurPath); |
| int saveCount = 0; |
| if (!focused) { |
| saveCount = canvas->save(); |
| canvas->clipPath(matchPath, SkRegion::kDifference_Op); |
| } |
| // Draw the blurred background |
| canvas->drawPath(blurPath, m_findBlurPaint); |
| if (!focused) { |
| canvas->restoreToCount(saveCount); |
| } |
| // Draw the foreground |
| canvas->drawPath(matchPath, m_findPaint); |
| } |
| |
| // Put a cap on the number of matches to draw. If the current page has more |
| // matches than this, only draw the focused match. |
| #define MAX_NUMBER_OF_MATCHES_TO_DRAW 101 |
| |
| void drawMatches(SkCanvas* canvas) |
| { |
| if (!m_matches || !m_matches->size()) { |
| return; |
| } |
| if (m_findIndex >= m_matches->size()) { |
| m_findIndex = 0; |
| } |
| const MatchInfo& matchInfo = (*m_matches)[m_findIndex]; |
| const SkRegion& currentMatchRegion = matchInfo.getLocation(); |
| const SkIRect& currentMatchBounds = currentMatchRegion.getBounds(); |
| int left = currentMatchBounds.fLeft; |
| int top = currentMatchBounds.fTop; |
| int right = currentMatchBounds.fRight; |
| int bottom = currentMatchBounds.fBottom; |
| WebCore::IntRect visible; |
| getVisibleRect(&visible); |
| // Check to make sure that the highlighted match is on screen. If not, |
| // scroll it onscreen and return. |
| int dx = 0; |
| if (left < visible.x()) { |
| dx = left - visible.x(); |
| // Only scroll right if the entire width can fit on screen. |
| } else if (right > visible.right() && right - left < visible.width()) { |
| dx = right - visible.right(); |
| } |
| int dy = 0; |
| if (top < visible.y()) { |
| dy = top - visible.y(); |
| // Only scroll down if the entire height can fit on screen |
| } else if (bottom > visible.bottom() && bottom - top < visible.height()) { |
| dy = bottom - visible.bottom(); |
| } |
| if ((dx|dy)) { |
| scrollBy(dx, dy); |
| viewInvalidate(); |
| return; |
| } |
| // Set up the paints used for drawing the matches |
| if (!m_isFindPaintSetUp) |
| setUpFindPaint(); |
| |
| // Draw the current match |
| drawMatch(currentMatchRegion, canvas, true); |
| // Now draw the picture, so that it shows up on top of the rectangle |
| canvas->drawPicture(*matchInfo.getPicture()); |
| |
| // Draw the rest |
| unsigned numberOfMatches = m_matches->size(); |
| if (numberOfMatches > 1 |
| && numberOfMatches < MAX_NUMBER_OF_MATCHES_TO_DRAW) { |
| SkIRect visibleIRect; |
| android_setrect(&visibleIRect, visible); |
| for(unsigned i = 0; i < numberOfMatches; i++) { |
| // The current match has already been drawn |
| if (i == m_findIndex) |
| continue; |
| const SkRegion& region = (*m_matches)[i].getLocation(); |
| // Do not draw matches which intersect the current one, or if it is |
| // offscreen |
| if (currentMatchRegion.intersects(region) |
| || !region.intersects(visibleIRect)) |
| continue; |
| drawMatch(region, canvas, false); |
| } |
| } |
| } |
| |
| void drawFocusRing(SkCanvas* canvas) |
| { |
| const CachedRoot* root = getFrameCache(AllowNewer); |
| if (!root) { |
| DBG_NAV_LOG("!root"); |
| m_followedLink = false; |
| return; |
| } |
| const CachedNode* node = root->currentFocus(); |
| if (!node) { |
| DBG_NAV_LOG("!node"); |
| m_followedLink = false; |
| return; |
| } |
| if (!node->hasFocusRing()) { |
| DBG_NAV_LOG("!node->hasFocusRing()"); |
| return; |
| } |
| const WTF::Vector<WebCore::IntRect>& rings = node->focusRings(); |
| if (!rings.size()) { |
| DBG_NAV_LOG("!rings.size()"); |
| return; |
| } |
| |
| bool isButton = false; |
| m_viewImpl->gButtonMutex.lock(); |
| // If this is a button drawn by us (rather than webkit) do not draw the |
| // focus ring, since its focus will be shown by a change in what we draw. |
| // Should be in sync with recordButtons, since that will be called |
| // before this. |
| if (m_viewImpl->m_buttons.size() > 0) { |
| WebCore::Node* focusPointer = (WebCore::Node*) node->nodePointer(); |
| Container* end = m_viewImpl->m_buttons.end(); |
| for (Container* ptr = m_viewImpl->m_buttons.begin(); ptr != end; ptr++) { |
| if (ptr->matches(focusPointer)) { |
| isButton = true; |
| break; |
| } |
| } |
| } |
| m_viewImpl->gButtonMutex.unlock(); |
| WebCore::IntRect bounds = node->bounds(); |
| bounds.inflate(SkScalarCeil(FOCUS_RING_OUTER_DIAMETER)); |
| SkRect sbounds; |
| android_setrect(&sbounds, bounds); |
| if (canvas->quickReject(sbounds, SkCanvas::kAA_EdgeType)) { |
| DBG_NAV_LOG("canvas->quickReject"); |
| m_followedLink = false; |
| return; |
| } |
| FocusRing::Flavor flavor = FocusRing::NORMAL_FLAVOR; |
| if (!isButton) { |
| flavor = node->type() != NORMAL_CACHEDNODETYPE ? |
| FocusRing::FAKE_FLAVOR : node->nodePointer() == m_invalidNode ? |
| FocusRing::INVALID_FLAVOR : FocusRing::NORMAL_FLAVOR; |
| if (flavor != FocusRing::INVALID_FLAVOR && m_followedLink) { |
| flavor = (FocusRing::Flavor) (flavor + FocusRing::NORMAL_ANIMATING); |
| } |
| #if DEBUG_NAV_UI |
| const WebCore::IntRect& ring = rings[0]; |
| DBG_NAV_LOGD("cachedFocusNode=%d (nodePointer=%p) flavor=%s rings=%d" |
| " (%d, %d, %d, %d)", node->index(), node->nodePointer(), |
| flavor == FocusRing::FAKE_FLAVOR ? "FAKE_FLAVOR" : |
| flavor == FocusRing::INVALID_FLAVOR ? "INVALID_FLAVOR" : |
| flavor == FocusRing::NORMAL_ANIMATING ? "NORMAL_ANIMATING" : |
| flavor == FocusRing::FAKE_ANIMATING ? "FAKE_ANIMATING" : "NORMAL_FLAVOR", |
| rings.size(), ring.x(), ring.y(), ring.width(), ring.height()); |
| #endif |
| } |
| if (isButton || flavor >= FocusRing::NORMAL_ANIMATING) { |
| SkMSec time = SkTime::GetMSecs(); |
| if (time < m_ringAnimationEnd) { |
| // views assume that inval bounds coordinates are non-negative |
| bounds.intersect(WebCore::IntRect(0, 0, INT_MAX, INT_MAX)); |
| postInvalidateDelayed(m_ringAnimationEnd - time, bounds); |
| } else { |
| m_followedLink = false; |
| flavor = (FocusRing::Flavor) (flavor - FocusRing::NORMAL_ANIMATING); |
| } |
| } |
| if (!isButton) |
| FocusRing::DrawRing(canvas, rings, flavor); |
| } |
| |
| OutOfFocusFix fixOutOfDateFocus(bool useReplay) |
| { |
| if (!m_frameCacheUI) { |
| DBG_NAV_LOG("!m_frameCacheUI"); |
| return DoNothing; |
| } |
| const CachedFrame* cachedFrame = 0; |
| const CachedNode* cachedFocusNode = m_frameCacheUI->currentFocus(&cachedFrame); |
| if (!cachedFocusNode) { |
| DBG_NAV_LOG("!cachedFocusNode"); |
| return DoNothing; |
| } |
| CachedRoot* webRoot = m_viewImpl->m_frameCacheKit; |
| if (!webRoot) { |
| DBG_NAV_LOG("!webRoot"); |
| return DoNothing; |
| } |
| int uiWidth = m_frameCacheUI->width(); |
| int webWidth = webRoot->width(); |
| if (uiWidth != webWidth) { |
| DBG_NAV_LOGD("uiWidth=%d webWidth=%d", uiWidth, webWidth); |
| return DoNothing; // allow text inputs to preserve their state |
| } else { |
| const WebCore::IntRect& cachedBounds = m_frameCacheUI->focusBounds(); |
| const CachedFrame* webFrame = 0; |
| const CachedNode* webFocusNode = webRoot->currentFocus(&webFrame); |
| DBG_NAV_LOGD("cachedBounds=(%d,%d,w=%d,h=%d) cachedFrame=%p (%d)" |
| " webFocusNode=%p (%d) webFrame=%p (%d)", |
| cachedBounds.x(), cachedBounds.y(), |
| cachedBounds.width(), cachedBounds.height(), |
| cachedFrame, cachedFrame ? cachedFrame->indexInParent() : -1, |
| webFocusNode, webFocusNode ? webFocusNode->index() : -1, |
| webFrame, webFrame ? webFrame->indexInParent() : -1); |
| if (webFocusNode && webFrame && webFrame->sameFrame(cachedFrame)) { |
| if (useReplay && !m_replay.count()) { |
| DBG_NAV_LOG("!m_replay.count()"); |
| return DoNothing; |
| } |
| if (webFocusNode->index() == cachedFocusNode->index()) { |
| DBG_NAV_LOG("index =="); |
| return DoNothing; |
| } |
| const WebCore::IntRect& webBounds = webRoot->focusBounds(); |
| DBG_NAV_LOGD("webBounds=(%d,%d,w=%d,h=%d)", |
| webBounds.x(), webBounds.y(), |
| webBounds.width(), webBounds.height()); |
| if (cachedBounds.contains(webBounds)) { |
| DBG_NAV_LOG("contains"); |
| return DoNothing; |
| } |
| if (webBounds.contains(cachedBounds)) { |
| DBG_NAV_LOG("webBounds contains"); |
| return DoNothing; |
| } |
| } |
| const CachedFrame* foundFrame = 0; |
| int x, y; |
| const CachedNode* found = findAt(webRoot, cachedBounds, &foundFrame, &x, &y); |
| #if DEBUG_NAV_UI |
| DBG_NAV_LOGD("found=%p (%d) frame=%p (%d)", |
| found, found ? found->index() : -1, |
| foundFrame, foundFrame ? foundFrame->indexInParent() : -1); |
| if (found) { |
| WebCore::IntRect newBounds = found->bounds(); |
| DBG_NAV_LOGD("found=(%d,%d,w=%d,h=%d) x=%d y=%d", |
| newBounds.x(), newBounds.y(), newBounds.width(), |
| newBounds.height(), x, y); |
| } |
| #endif |
| webRoot->setCachedFocus(const_cast<CachedFrame*>(foundFrame), |
| const_cast<CachedNode*>(found)); |
| if (found) |
| webRoot->rootHistory()->setNavBounds(found->bounds()); |
| WebCore::Frame* framePointer = foundFrame ? (WebCore::Frame*) foundFrame->framePointer() : 0; |
| WebCore::Node* nodePointer = found ? (WebCore::Node*) found->nodePointer() : 0; |
| setFocusData(webRoot->generation(), framePointer, nodePointer, x, y, !found); |
| sendFinalFocus(framePointer, nodePointer, x, y); |
| if (found && (found->isTextArea() || found->isTextField())) |
| return UpdateTextEntry; |
| } |
| checkOldFocus: |
| return cachedFocusNode->isTextArea() || cachedFocusNode->isTextField() ? ClearTextEntry : DoNothing; |
| } |
| |
| bool focusIsTextArea(FrameCachePermission allowNewer) |
| { |
| CachedRoot* root = getFrameCache(allowNewer); |
| if (!root) { |
| DBG_NAV_LOG("!root"); |
| return false; |
| } |
| const CachedNode* focus = root->currentFocus(); |
| if (!focus) |
| return false; |
| return focus->isTextArea() || focus->isTextField(); |
| } |
| |
| void focusRingBounds(WebCore::IntRect* bounds) |
| { |
| DBG_NAV_LOGD("%s", ""); |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (root) { |
| const CachedNode* cachedNode = root->currentFocus(); |
| if (cachedNode) { |
| cachedNode->focusRingBounds(bounds); |
| DBG_NAV_LOGD("bounds={%d,%d,%d,%d}", bounds->x(), bounds->y(), |
| bounds->width(), bounds->height()); |
| return; |
| } |
| } |
| *bounds = WebCore::IntRect(0, 0, 0, 0); |
| } |
| |
| CachedRoot* getFrameCache(FrameCachePermission allowNewer) |
| { |
| if (!m_viewImpl->m_updatedFrameCache) |
| return m_frameCacheUI; |
| m_viewImpl->gRecomputeFocusMutex.lock(); |
| bool recomputeInProgress = m_viewImpl->m_recomputeEvents.size() > 0; |
| m_viewImpl->gRecomputeFocusMutex.unlock(); |
| if (allowNewer != AllowNewest && recomputeInProgress) |
| return m_frameCacheUI; |
| if (allowNewer == DontAllowNewer && m_viewImpl->m_lastGeneration < m_generation) |
| return m_frameCacheUI; |
| DBG_NAV_LOGD("%s", "m_viewImpl->m_updatedFrameCache == true"); |
| m_viewImpl->gFrameCacheMutex.lock(); |
| OutOfFocusFix fix = DoNothing; |
| if (allowNewer != DontAllowNewer) |
| fix = fixOutOfDateFocus(m_viewImpl->m_useReplay); |
| delete m_frameCacheUI; |
| delete m_navPictureUI; |
| m_viewImpl->m_updatedFrameCache = false; |
| m_frameCacheUI = m_viewImpl->m_frameCacheKit; |
| m_navPictureUI = m_viewImpl->m_navPictureKit; |
| m_viewImpl->m_frameCacheKit = 0; |
| m_viewImpl->m_navPictureKit = 0; |
| m_viewImpl->gFrameCacheMutex.unlock(); |
| if (fix == UpdateTextEntry) |
| updateTextEntry(); |
| else if (fix == ClearTextEntry) |
| clearTextEntry(); |
| return m_frameCacheUI; |
| } |
| |
| int getScaledMaxXScroll() |
| { |
| LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| int result = env->CallIntMethod(m_javaGlue.object(env).get(), m_javaGlue.m_getScaledMaxXScroll); |
| checkException(env); |
| return result; |
| } |
| |
| int getScaledMaxYScroll() |
| { |
| LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| int result = env->CallIntMethod(m_javaGlue.object(env).get(), m_javaGlue.m_getScaledMaxYScroll); |
| checkException(env); |
| return result; |
| } |
| |
| void getVisibleRect(WebCore::IntRect* rect) |
| { |
| LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| jobject jRect = env->CallObjectMethod(m_javaGlue.object(env).get(), m_javaGlue.m_getVisibleRect); |
| checkException(env); |
| int left = (int) env->GetIntField(jRect, m_javaGlue.m_rectLeft); |
| checkException(env); |
| rect->setX(left); |
| int top = (int) env->GetIntField(jRect, m_javaGlue.m_rectTop); |
| checkException(env); |
| rect->setY(top); |
| int width = (int) env->CallIntMethod(jRect, m_javaGlue.m_rectWidth); |
| checkException(env); |
| rect->setWidth(width); |
| int height = (int) env->CallIntMethod(jRect, m_javaGlue.m_rectHeight); |
| checkException(env); |
| rect->setHeight(height); |
| env->DeleteLocalRef(jRect); |
| checkException(env); |
| } |
| |
| static CachedFrame::Direction KeyToDirection(KeyCode keyCode) |
| { |
| switch (keyCode) { |
| case kKeyCodeDpadRight: |
| DBG_NAV_LOGD("keyCode=%s", "right"); |
| return CachedFrame::RIGHT; |
| case kKeyCodeDpadLeft: |
| DBG_NAV_LOGD("keyCode=%s", "left"); |
| return CachedFrame::LEFT; |
| case kKeyCodeDpadDown: |
| DBG_NAV_LOGD("keyCode=%s", "down"); |
| return CachedFrame::DOWN; |
| case kKeyCodeDpadUp: |
| DBG_NAV_LOGD("keyCode=%s", "up"); |
| return CachedFrame::UP; |
| default: |
| LOGD("------- bad key sent to WebView::moveFocus"); |
| return CachedFrame::UNINITIALIZED; |
| } |
| } |
| |
| bool invalidFrame(WebCore::Frame* frame, const CachedRoot* root) |
| { |
| if (!frame) |
| return false; |
| int frameBuild = m_viewImpl->retrieveFrameGeneration(frame); |
| int rootBuild = root->generation(); |
| return frameBuild > rootBuild; |
| } |
| |
| WebCore::String imageURI(int x, int y) |
| { |
| const CachedRoot* root = getFrameCache(DontAllowNewer); |
| return root ? root->imageURI(x, y) : WebCore::String(); |
| } |
| |
| bool focusNodeWantsKeyEvents() |
| { |
| const CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (root) { |
| const CachedNode* focus = root->currentFocus(); |
| if (focus) { |
| return focus->isWantsKeyEvents(); |
| } |
| } |
| return false; |
| } |
| |
| /* returns true if the key had no effect (neither scrolled nor changed focus) */ |
| bool moveFocus(int keyCode, int count, bool ignoreScroll, bool inval, |
| void* lastSentFocus, const WebCore::IntRect* lastSentBounds) |
| { |
| CachedRoot* root = getFrameCache(AllowNewer); |
| if (!root) { |
| DBG_NAV_LOG("!root"); |
| setFocusData(0, 0, 0, 0, 0, true); |
| sendKitFocus(); // will build cache and retry |
| FirstMoveFocusParams params; |
| params.d.m_trigger = CommonParams::FirstMoveFocusParams; |
| params.d.m_generation = m_generation; |
| params.m_keyCode = keyCode; |
| params.m_count = count; |
| params.m_ignoreScroll = ignoreScroll; |
| m_replay.add(params.d, sizeof(params)); |
| return true; |
| } |
| |
| CachedFrame::Direction direction = KeyToDirection((KeyCode) keyCode); |
| const CachedFrame* cachedFrame, * oldFrame = 0; |
| const CachedNode* focus = root->currentFocus(&oldFrame); |
| WebCore::IntPoint focusLocation = root->focusLocation(); |
| DBG_NAV_LOGD("old focus %d (nativeNode=%p) focusLocation={%d, %d}", |
| focus ? focus->index() : 0, |
| focus ? focus->nodePointer() : 0, focusLocation.x(), focusLocation.y()); |
| WebCore::IntRect visibleRect; |
| getVisibleRect(&visibleRect); |
| DBG_NAV_LOGD("getVisibleRect %d,%d,%d,%d", |
| visibleRect.x(), visibleRect.y(), visibleRect.width(), visibleRect.height()); |
| root->setVisibleRect(visibleRect); |
| int xMax = getScaledMaxXScroll(); |
| int yMax = getScaledMaxYScroll(); |
| root->setMaxScroll(xMax, yMax); |
| CachedHistory savedHistory = *root->rootHistory(); |
| bool oldNodeIsTextArea = focusIsTextArea(DontAllowNewer); |
| const CachedNode* cachedNode = 0; |
| int dx = 0; |
| int dy = 0; |
| int counter = count; |
| if (!focus || !focus->isInput() || !m_followedLink) |
| root->setScrollOnly(m_followedLink); |
| while (--counter >= 0) { |
| WebCore::IntPoint scroll = WebCore::IntPoint(0, 0); |
| cachedNode = root->moveFocus(direction, &cachedFrame, &scroll); |
| dx += scroll.x(); |
| dy += scroll.y(); |
| } |
| DBG_NAV_LOGD("new focus %d (nativeNode=%p) focusLocation={%d, %d}", |
| cachedNode ? cachedNode->index() : 0, |
| cachedNode ? cachedNode->nodePointer() : 0, root->focusLocation().x(), |
| root->focusLocation().y()); |
| // If !m_heightCanMeasure (such as in the browser), we want to scroll no |
| // matter what |
| if (!ignoreScroll && (!m_heightCanMeasure || |
| !cachedNode || |
| (focus && focus->nodePointer() == cachedNode->nodePointer()))) |
| { |
| if (count == 1 && dx != 0 && dy == 0 && -m_lastDx == dx && |
| SkTime::GetMSecs() - m_lastDxTime < 1000) |
| root->checkForJiggle(&dx); |
| DBG_NAV_LOGD("scrollBy %d,%d", dx, dy); |
| if ((dx | dy)) |
| this->scrollBy(dx, dy); |
| m_lastDx = dx; |
| m_lastDxTime = SkTime::GetMSecs(); |
| ignoreScroll = true; // if move re-executes, don't scroll the second time |
| } |
| bool result = false; |
| if (cachedNode) { |
| WebCore::IntPoint pos; |
| root->setCachedFocus((CachedFrame*) cachedFrame, (CachedNode*) cachedNode); |
| root->getSimulatedMousePosition(&pos); |
| if (lastSentFocus == cachedNode->nodePointer() && lastSentBounds && |
| *lastSentBounds == cachedNode->bounds()) |
| { |
| sendFinalFocus((WebCore::Frame*) cachedFrame->framePointer(), |
| (WebCore::Node*) cachedNode->nodePointer(), pos.x(), pos.y()); |
| } else { |
| setFocusData(root->generation(), |
| (WebCore::Frame*) cachedFrame->framePointer(), |
| (WebCore::Node*) cachedNode->nodePointer(), pos.x(), pos.y(), |
| true); |
| sendKitFocus(); |
| if (inval) |
| viewInvalidate(); |
| MoveFocusParams params; |
| params.d.d.m_trigger = CommonParams::MoveFocusParams; |
| params.d.d.m_generation = m_generation; |
| params.c.setFocus(focus, oldFrame, root, focusLocation); |
| params.m_sentFocus = cachedNode->nodePointer(); |
| params.m_sentBounds = cachedNode->bounds(); |
| params.m_visibleRect = visibleRect; |
| params.m_history = savedHistory; |
| DBG_NAV_LOGD("history.mDidFirstLayout=%s", |
| params.m_history.didFirstLayout() ? "true" : "false"); |
| params.m_xMax = xMax; |
| params.m_yMax = yMax; |
| params.d.m_keyCode = keyCode; |
| params.d.m_count = count; |
| params.d.m_ignoreScroll = ignoreScroll; |
| m_replay.add(params.d.d, sizeof(params)); |
| } |
| } else { |
| if (visibleRect.intersects(root->focusBounds()) == false) { |
| setFocusData(root->generation(), 0, 0, 0, 0, true); |
| sendKitFocus(); // will build cache and retry |
| } |
| FirstMoveFocusParams params; |
| params.d.m_trigger = CommonParams::FirstMoveFocusParams; |
| params.d.m_generation = m_generation; |
| params.m_keyCode = keyCode; |
| params.m_count = count; |
| params.m_ignoreScroll = ignoreScroll; |
| m_replay.add(params.d, sizeof(params)); |
| int docHeight = root->documentHeight(); |
| int docWidth = root->documentWidth(); |
| if (visibleRect.bottom() + dy > docHeight) |
| dy = docHeight - visibleRect.bottom(); |
| else if (visibleRect.y() + dy < 0) |
| dy = -visibleRect.y(); |
| if (visibleRect.right() + dx > docWidth) |
| dx = docWidth - visibleRect.right(); |
| else if (visibleRect.x() < 0) |
| dx = -visibleRect.x(); |
| result = direction == CachedFrame::LEFT ? dx >= 0 : |
| direction == CachedFrame::RIGHT ? dx <= 0 : |
| direction == CachedFrame::UP ? dy >= 0 : dy <= 0; |
| } |
| if (focusIsTextArea(DontAllowNewer)) |
| updateTextEntry(); |
| else if (oldNodeIsTextArea) |
| clearTextEntry(); |
| return result; |
| } |
| |
| void notifyFocusSet(FrameCachePermission inEditingMode) |
| { |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (root) { |
| // make sure the mFocusData in WebView.java is in sync with WebView.cpp |
| const CachedFrame* frame = 0; |
| const CachedNode* node = root->currentFocus(&frame); |
| const WebCore::IntPoint& focusLocation = root->focusLocation(); |
| setFocusData(root->generation(), |
| frame ? (WebCore::Frame*) frame->framePointer() : 0, |
| node ? (WebCore::Node*) node->nodePointer() : 0, |
| focusLocation.x(), focusLocation.y(), false); |
| } |
| |
| if (focusIsTextArea(inEditingMode)) |
| updateTextEntry(); |
| else if (inEditingMode) |
| clearTextEntry(); |
| #if DEBUG_NAV_UI |
| if (m_frameCacheUI) { |
| const CachedNode* focus = m_frameCacheUI->currentFocus(); |
| DBG_NAV_LOGD("focus %d (nativeNode=%p)", |
| focus ? focus->index() : 0, |
| focus ? focus->nodePointer() : 0); |
| } |
| #endif |
| } |
| |
| void notifyProgressFinished() |
| { |
| DBG_NAV_LOGD("focusIsTextArea=%d", focusIsTextArea(DontAllowNewer)); |
| updateTextEntry(); |
| #if DEBUG_NAV_UI |
| if (m_frameCacheUI) { |
| const CachedNode* focus = m_frameCacheUI->currentFocus(); |
| DBG_NAV_LOGD("focus %d (nativeNode=%p)", |
| focus ? focus->index() : 0, |
| focus ? focus->nodePointer() : 0); |
| } |
| #endif |
| } |
| |
| void recomputeFocus() |
| { |
| int generation; |
| do { |
| m_viewImpl->gRecomputeFocusMutex.lock(); |
| if (!m_viewImpl->m_recomputeEvents.size()) { |
| m_viewImpl->gRecomputeFocusMutex.unlock(); |
| return; |
| } |
| generation = m_viewImpl->m_recomputeEvents.first(); |
| m_viewImpl->m_recomputeEvents.remove(0); |
| m_viewImpl->gRecomputeFocusMutex.unlock(); |
| DBG_NAV_LOGD("generation=%d", generation); |
| CachedRoot* root = getFrameCache(AllowNewest); |
| if (!root) { |
| DBG_NAV_LOG("!root"); |
| return; |
| } |
| LargestParams storage; |
| const CommonParams& params = storage.d.d; |
| char* pos = m_replay.position(); |
| while (m_replay.retrieve(&storage.d.d) < generation) |
| DBG_NAV_LOGD("dropped ", params.m_generation); |
| if (params.m_generation > generation) { |
| DBG_NAV_LOGD("params.m_generation=%d > generation=%d", |
| params.m_generation, generation); |
| m_replay.rewind(pos); |
| return; |
| } |
| int lastAdd = m_replay.lastAdd(); |
| do { |
| LOG_ASSERT(params.m_trigger != CommonParams::NoData, "expected data"); |
| bool inval = generation == m_generation; |
| switch (params.m_trigger) { |
| case CommonParams::ClearFocusParams: { |
| const ClearFocusParams& sParams = *(ClearFocusParams*) &storage; |
| const CacheParams& cParams = sParams.c; |
| if (invalidFrame(cParams.m_frame, root)) { |
| DBG_NAV_LOGD("dropped %s generation=%d", |
| TriggerNames[params.m_trigger], generation); |
| return; |
| } |
| root->setFocus(cParams.m_frame, cParams.m_node, cParams.m_x, cParams.m_y); |
| clearFocus(sParams.m_x, sParams.m_y, inval); |
| DBG_NAV_LOGD("clearFocus(x,y)={%d,%d}", sParams.m_x, sParams.m_y); |
| } break; |
| case CommonParams::MotionUpParams: { |
| const MotionUpParams& mParams = *(MotionUpParams*) &storage; |
| // const CacheParams& cParams = mParams.c; |
| // if (invalidFrame(cParams.m_frame, root) == false) |
| // root->setFocus(cParams.m_frame, cParams.m_node, |
| // cParams.m_x, cParams.m_y); |
| motionUp(mParams.m_x, mParams.m_y, mParams.m_slop, mParams.m_isClick, inval, true); |
| DBG_NAV_LOGD("motionUp m_x=%d m_y=%d", mParams.m_x, mParams.m_y); |
| } break; |
| case CommonParams::FirstMoveFocusParams: { |
| if (invalidFrame((WebCore::Frame*) root->framePointer(), root)) { |
| DBG_NAV_LOGD("dropped %s generation=%d", |
| TriggerNames[params.m_trigger], generation); |
| return; |
| } |
| const FirstMoveFocusParams& fParams = *(FirstMoveFocusParams*) &storage; |
| DBG_NAV_LOGD("first moveFocus keyCode=%d count=%d" |
| " ignoreScroll=%s", fParams.m_keyCode, fParams.m_count, |
| fParams.m_ignoreScroll ? "true" : "false"); |
| moveFocus(fParams.m_keyCode, fParams.m_count, |
| fParams.m_ignoreScroll, inval, 0, 0); |
| } break; |
| case CommonParams::MoveFocusParams: { |
| const MoveFocusParams& mParams = *(MoveFocusParams*) &storage; |
| const CacheParams& cParams = mParams.c; |
| if (invalidFrame(cParams.m_frame, root)) { |
| DBG_NAV_LOGD("dropped %s generation=%d", |
| TriggerNames[params.m_trigger], generation); |
| return; |
| } |
| DBG_NAV_LOGD("moveFocus keyCode=%d count=%d ignoreScroll=%s " |
| "history.mDidFirstLayout=%s", mParams.d.m_keyCode, |
| mParams.d.m_count, mParams.d.m_ignoreScroll ? "true" : "false", |
| mParams.m_history.didFirstLayout() ? "true" : "false"); |
| if (!root->setFocus(cParams.m_frame, cParams.m_node, |
| cParams.m_x, cParams.m_y)) { |
| DBG_NAV_LOGD("can't restore focus frame=%p node=%p", |
| "x=%d y=%d %s", cParams.m_frame, cParams.m_node, |
| cParams.m_x, cParams.m_y, TriggerNames[params.m_trigger]); |
| return; |
| } |
| root->setVisibleRect(mParams.m_visibleRect); |
| root->setMaxScroll(mParams.m_xMax, mParams.m_yMax); |
| *root->rootHistory() = mParams.m_history; |
| moveFocus(mParams.d.m_keyCode, mParams.d.m_count, |
| mParams.d.m_ignoreScroll, inval, |
| mParams.m_sentFocus, &mParams.m_sentBounds); |
| } break; |
| default: |
| LOG_ASSERT(0, "unknown trigger"); |
| } |
| if (params.m_generation >= lastAdd) |
| break; |
| root = getFrameCache(DontAllowNewer); // re-execution may have retrieved newer cache |
| m_replay.retrieve(&storage.d.d); |
| DBG_NAV_LOGD("continuation m_generation %d", params.m_generation); |
| } while (true); |
| } while (true); |
| } |
| |
| void resetFocus() |
| { |
| DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); |
| CachedRoot* root = getFrameCache(AllowNewer); |
| if (!root) |
| return; |
| root->setCachedFocus(0, 0); |
| } |
| |
| const CachedNode* findAt(CachedRoot* root, const WebCore::IntRect& rect, |
| const CachedFrame** framePtr, int* rxPtr, int* ryPtr) |
| { |
| *rxPtr = 0; |
| *ryPtr = 0; |
| *framePtr = 0; |
| if (!root) |
| return 0; |
| WebCore::IntRect visibleRect; |
| getVisibleRect(&visibleRect); |
| root->setVisibleRect(visibleRect); |
| return root->findAt(rect, framePtr, rxPtr, ryPtr); |
| } |
| |
| void selectBestAt(const WebCore::IntRect& rect) |
| { |
| const CachedFrame* frame; |
| int rx, ry; |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| const CachedNode* node = findAt(root, rect, &frame, &rx, &ry); |
| int rootGeneration = root ? root->generation() : 0; |
| setFocusData(rootGeneration, |
| frame ? (WebCore::Frame*) frame->framePointer() : 0, |
| node ? (WebCore::Node*) node->nodePointer() : 0, rx, ry, false); |
| if (!node) { |
| DBG_NAV_LOGD("no nodes found root=%p", root); |
| if (root) { |
| root->clearFocus(); |
| root->setCachedFocus(0, 0); |
| } |
| sendKitFocus(); |
| viewInvalidate(); |
| clearTextEntry(); |
| return; |
| } |
| DBG_NAV_LOGD("CachedNode:%p (%d)", node, node->index()); |
| const CachedFrame* oldFrame = 0; |
| const CachedNode* oldFocusNode = root->currentFocus(&oldFrame); |
| bool oldNodeIsTextArea = focusIsTextArea(DontAllowNewer); |
| root->setCachedFocus(const_cast<CachedFrame*>(frame), |
| const_cast<CachedNode*>(node)); |
| viewInvalidate(); |
| if (focusIsTextArea(DontAllowNewer)) |
| updateTextEntry(); |
| else if (oldNodeIsTextArea) |
| clearTextEntry(); |
| } |
| |
| WebCore::IntRect getNavBounds() |
| { |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (!root) |
| return WebCore::IntRect(0, 0, 0, 0); |
| return root->rootHistory()->navBounds(); |
| } |
| |
| void setNavBounds(const WebCore::IntRect& rect) |
| { |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (!root) |
| return; |
| root->rootHistory()->setNavBounds(rect); |
| } |
| |
| void markNodeInvalid(WebCore::Node* node) |
| { |
| DBG_NAV_LOGD("node=%p", node); |
| m_invalidNode = node; |
| viewInvalidate(); |
| } |
| |
| bool motionUp(int x, int y, int slop, bool isClick, bool inval, bool retry) |
| { |
| bool pageScrolled = false; |
| m_followedLink = false; |
| const CachedFrame* frame; |
| WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop, slop * 2, slop * 2); |
| int rx, ry; |
| CachedRoot* root = getFrameCache(AllowNewer); |
| const CachedNode* result = findAt(root, rect, &frame, &rx, &ry); |
| if (!result) { |
| DBG_NAV_LOGD("no nodes found root=%p", root); |
| int rootGeneration = 0; |
| if (root) { |
| root->clearFocus(); |
| rootGeneration = root->generation(); |
| if (!retry) { // scroll first time only |
| int dx = root->checkForCenter(x, y); |
| if (dx) { |
| scrollBy(dx, 0); |
| retry = true; // don't recompute later since we scrolled |
| pageScrolled = true; |
| } |
| } |
| } |
| sendMotionUp(rootGeneration, frame ? |
| (WebCore::Frame*) frame->framePointer() : 0, |
| 0, x, y, slop, isClick, retry); |
| if (inval) |
| viewInvalidate(); |
| if (!retry) { |
| MotionUpParams params; |
| params.d.m_trigger = CommonParams::MotionUpParams; |
| params.d.m_generation = m_generation; |
| params.m_x = x; |
| params.m_y = y; |
| params.m_slop = slop; |
| params.m_isClick = isClick; |
| m_replay.add(params.d, sizeof(params)); |
| } |
| clearTextEntry(); |
| return pageScrolled; |
| } |
| DBG_NAV_LOGD("CachedNode:%p (%d) x=%d y=%d rx=%d ry=%d", result, |
| result->index(), x, y, rx, ry); |
| // const CachedFrame* oldFrame = 0; |
| // const CachedNode* oldFocusNode = root->currentFocus(&oldFrame); |
| // WebCore::IntPoint focusLocation = root->focusLocation(); |
| bool oldNodeIsTextArea = !retry && focusIsTextArea(DontAllowNewer); |
| root->setCachedFocus(const_cast<CachedFrame*>(frame), |
| const_cast<CachedNode*>(result)); |
| bool newNodeIsTextArea = focusIsTextArea(DontAllowNewer); |
| CachedNodeType type = result->type(); |
| if (type == NORMAL_CACHEDNODETYPE || newNodeIsTextArea) { |
| sendMotionUp(root->generation(), |
| frame ? (WebCore::Frame*) frame->framePointer() : 0, |
| result ? (WebCore::Node*) result->nodePointer() : 0, rx, ry, |
| slop, isClick, retry); |
| if (inval) |
| viewInvalidate(); |
| if (!retry) { |
| MotionUpParams params; |
| params.d.m_trigger = CommonParams::MotionUpParams; |
| params.d.m_generation = m_generation; |
| params.m_x = x; |
| params.m_y = y; |
| params.m_slop = slop; |
| params.m_isClick = isClick; |
| // params.c.setFocus(oldFocusNode, oldFrame, root, focusLocation); |
| m_replay.add(params.d, sizeof(params)); |
| } |
| } else if (inval) |
| viewInvalidate(); |
| if (newNodeIsTextArea) { |
| updateTextEntry(); |
| displaySoftKeyboard(); |
| } else { |
| if (isClick) { |
| setFollowedLink(true); |
| if (type != NORMAL_CACHEDNODETYPE) { |
| overrideUrlLoading(result->getExport()); |
| } |
| } |
| if (oldNodeIsTextArea) |
| clearTextEntry(); |
| } |
| return pageScrolled; |
| } |
| |
| void overrideUrlLoading(const WebCore::String& url) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| jstring jName = env->NewString((jchar*) url.characters(), url.length()); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), |
| m_javaGlue.m_overrideLoading, jName); |
| env->DeleteLocalRef(jName); |
| } |
| |
| void setFindIsUp(bool up) |
| { |
| m_viewImpl->m_findIsUp = up; |
| if (!up) |
| m_hasCurrentLocation = false; |
| } |
| |
| void setFollowedLink(bool followed) |
| { |
| if ((m_followedLink = followed) != false) { |
| m_ringAnimationEnd = SkTime::GetMSecs() + 500; |
| viewInvalidate(); |
| } |
| } |
| |
| void setHeightCanMeasure(bool measure) |
| { |
| m_heightCanMeasure = measure; |
| } |
| |
| SkIRect m_selStart, m_selEnd; |
| SkRegion m_selRegion; |
| #define MIN_ARROW_DISTANCE (20 * 20) |
| |
| void moveSelection(int x, int y, bool extendSelection) |
| { |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (!root) |
| return; |
| const SkPicture& picture = *m_navPictureUI; |
| WebCore::IntRect r; |
| getVisibleRect(&r); |
| SkIRect area; |
| area.set(r.x(), r.y(), r.right(), r.bottom()); |
| if (!extendSelection) |
| m_selStart = m_selEnd = CopyPaste::findClosest(picture, area, x, y); |
| else |
| m_selEnd = CopyPaste::findClosest(picture, area, x, y); |
| DBG_NAV_LOGD("x=%d y=%d extendSelection=%s m_selStart=(%d, %d, %d, %d)" |
| " m_selEnd=(%d, %d, %d, %d)", x, y, extendSelection ? "true" : "false", |
| m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, |
| m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); |
| } |
| |
| const SkRegion& getSelection() |
| { |
| return m_selRegion; |
| } |
| |
| void drawSelection(SkCanvas* canvas, int x, int y, bool extendSelection) |
| { |
| if (!extendSelection) { |
| int dx = x - m_selStart.fLeft; |
| dx *= dx; |
| int otherX = x - m_selStart.fRight; |
| if (dx > (otherX *= otherX)) |
| dx = otherX; |
| int dy = y - m_selStart.fTop; |
| int dist = dx * dx + dy * dy; |
| if (dist > MIN_ARROW_DISTANCE) |
| drawSelectionArrow(canvas, x, y); |
| else |
| drawSelectionPointer(canvas, x, y, true); |
| } else { |
| drawSelectionRegion(canvas); |
| drawSelectionPointer(canvas, x, y, false); |
| } |
| } |
| |
| void drawSelectionRegion(SkCanvas* canvas) |
| { |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (!root) |
| return; |
| WebCore::IntRect r; |
| getVisibleRect(&r); |
| SkIRect area; |
| area.set(r.x(), r.y(), r.right(), r.bottom()); |
| m_selRegion.setEmpty(); |
| CopyPaste::buildSelection(*m_navPictureUI, area, m_selStart, m_selEnd, &m_selRegion); |
| SkPath path; |
| m_selRegion.getBoundaryPath(&path); |
| SkPaint paint; |
| paint.setAntiAlias(true); |
| paint.setColor(SkColorSetARGB(0x40, 255, 51, 204)); |
| canvas->drawPath(path, paint); |
| } |
| |
| void drawSelectionPointer(SkCanvas* canvas, int x, int y, bool gridded) |
| { |
| SkPath path; |
| getSelectionCaret(&path); |
| SkPaint paint; |
| paint.setAntiAlias(true); |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setColor(SK_ColorBLACK); |
| SkPixelXorXfermode xorMode(SK_ColorWHITE); |
| paint.setXfermode(&xorMode); |
| int sc = canvas->save(); |
| if (gridded) { |
| bool useLeft = x <= (m_selStart.fLeft + m_selStart.fRight) >> 1; |
| canvas->translate(SkIntToScalar(useLeft ? m_selStart.fLeft : |
| m_selStart.fRight), SkIntToScalar(m_selStart.fTop)); |
| } else |
| canvas->translate(SkIntToScalar(x), SkIntToScalar(y)); |
| canvas->drawPath(path, paint); |
| canvas->restoreToCount(sc); |
| } |
| |
| void drawSelectionArrow(SkCanvas* canvas, int x, int y) |
| { |
| SkPath path; |
| getSelectionArrow(&path); |
| SkPaint paint; |
| paint.setAntiAlias(true); |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setColor(SK_ColorBLACK); |
| paint.setStrokeWidth(SK_Scalar1 * 2); |
| int sc = canvas->save(); |
| canvas->translate(SkIntToScalar(x), SkIntToScalar(y)); |
| canvas->drawPath(path, paint); |
| paint.setStyle(SkPaint::kFill_Style); |
| paint.setColor(SK_ColorWHITE); |
| canvas->drawPath(path, paint); |
| canvas->restoreToCount(sc); |
| } |
| |
| void getSelectionArrow(SkPath* path) |
| { |
| const int arrow[] = { |
| 0, 14, 3, 11, 5, 15, 9, 15, 7, 11, 11, 11 |
| }; |
| for (unsigned index = 0; index < sizeof(arrow)/sizeof(arrow[0]); index += 2) |
| path->lineTo(SkIntToScalar(arrow[index]), SkIntToScalar(arrow[index + 1])); |
| path->close(); |
| } |
| |
| void getSelectionCaret(SkPath* path) |
| { |
| SkScalar height = SkIntToScalar(m_selStart.fBottom - m_selStart.fTop); |
| SkScalar dist = height / 4; |
| path->lineTo(0, height); |
| SkScalar bottom = height + dist; |
| path->lineTo(-dist, bottom); |
| SkScalar edge = bottom - SK_Scalar1/2; |
| path->moveTo(-dist, edge); |
| path->lineTo(dist, edge); |
| path->moveTo(dist, bottom); |
| path->lineTo(0, height); |
| } |
| |
| void sendFinalFocus(WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y) |
| { |
| DBG_NAV_LOGD("framePtr=%p nodePtr=%p x=%d y=%d", framePtr, nodePtr, x, y); |
| LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_sendFinalFocus, |
| (jint) framePtr, (jint) nodePtr, x, y); |
| checkException(env); |
| } |
| |
| void sendKitFocus() |
| { |
| LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_sendKitFocus); |
| checkException(env); |
| } |
| |
| void sendMotionUp(int buildGeneration, |
| WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y, int slop, |
| bool isClick, bool retry) |
| { |
| m_viewImpl->m_touchGeneration = m_viewImpl->m_generation = ++m_generation; |
| DBG_NAV_LOGD("buildGeneration=%d m_generation=%d framePtr=%p nodePtr=%p" |
| " x=%d y=%d slop=%d", buildGeneration, |
| m_generation, framePtr, nodePtr, x, y, slop); |
| LOG_ASSERT(m_javaGlue.m_obj, "A WebView was not associated with this WebViewNative!"); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_sendMotionUp, m_generation, |
| buildGeneration, (jint) framePtr, (jint) nodePtr, x, y, slop, isClick, retry); |
| checkException(env); |
| } |
| |
| void setFocusData(int buildGeneration, WebCore::Frame* framePtr, |
| WebCore::Node* nodePtr, int x, int y, bool ignoreNullFocus) |
| { |
| m_viewImpl->m_moveGeneration = m_viewImpl->m_generation = ++m_generation; |
| DBG_NAV_LOGD("moveGeneration=%d buildGeneration=%d framePtr=%p nodePtr=%p" |
| " x=%d y=%d", m_generation, buildGeneration, framePtr, nodePtr, x, y); |
| LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_setFocusData, m_generation, |
| buildGeneration, (jint) framePtr, (jint) nodePtr, x, y, ignoreNullFocus); |
| checkException(env); |
| } |
| |
| // This function is only used by findNext and setMatches. In it, we store |
| // upper left corner of the match specified by m_findIndex in |
| // m_currentMatchLocation. |
| void inline storeCurrentMatchLocation() |
| { |
| SkASSERT(m_findIndex < m_matches->size()); |
| const SkIRect& bounds = (*m_matches)[m_findIndex].getLocation().getBounds(); |
| m_currentMatchLocation.set(bounds.fLeft, bounds.fTop); |
| m_hasCurrentLocation = true; |
| } |
| |
| void findNext(bool forward) |
| { |
| if (!m_matches || !m_matches->size()) |
| return; |
| if (forward) { |
| m_findIndex++; |
| if (m_findIndex == m_matches->size()) |
| m_findIndex = 0; |
| } else { |
| if (m_findIndex == 0) { |
| m_findIndex = m_matches->size() - 1; |
| } else { |
| m_findIndex--; |
| } |
| } |
| storeCurrentMatchLocation(); |
| viewInvalidate(); |
| } |
| |
| // With this call, WebView takes ownership of matches, and is responsible for |
| // deleting it. |
| void setMatches(WTF::Vector<MatchInfo>* matches) |
| { |
| if (m_matches) |
| delete m_matches; |
| m_matches = matches; |
| if (m_matches->size()) { |
| if (m_hasCurrentLocation) { |
| for (unsigned i = 0; i < m_matches->size(); i++) { |
| const SkIRect& rect = (*m_matches)[i].getLocation().getBounds(); |
| if (rect.fLeft == m_currentMatchLocation.fX |
| && rect.fTop == m_currentMatchLocation.fY) { |
| m_findIndex = i; |
| viewInvalidate(); |
| return; |
| } |
| } |
| } |
| // If we did not have a stored location, or if we were unable to restore |
| // it, store the new one. |
| m_findIndex = 0; |
| storeCurrentMatchLocation(); |
| } else { |
| m_hasCurrentLocation = false; |
| } |
| viewInvalidate(); |
| } |
| |
| void scrollBy(int dx, int dy) |
| { |
| LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); |
| |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_scrollBy, |
| dx, dy, true); |
| checkException(env); |
| } |
| |
| bool updateFocusNode(JNIEnv* env) |
| { |
| CachedRoot* root = getFrameCache(DontAllowNewer); |
| if (!root) { |
| DBG_NAV_LOG("!root"); |
| return false; |
| } |
| const CachedFrame* cachedFrame = 0; |
| const CachedNode* cachedFocusNode = root->currentFocus(&cachedFrame); |
| if (!cachedFocusNode) { |
| DBG_NAV_LOG("!cachedFocusNode"); |
| return false; |
| } |
| DBG_NAV_LOGD("cachedFocusNode=%d (nodePointer=%p)", |
| cachedFocusNode->index(), |
| cachedFocusNode->nodePointer()); |
| jobject focusnode = env->GetObjectField(m_javaGlue.object(env).get(), m_javaGlue.m_focusNode); |
| LOG_ASSERT(focusnode, "Could not find WebView's FocusNode"); |
| |
| bool isTextArea = cachedFocusNode->isTextArea(); |
| bool isTextField = cachedFocusNode->isTextField(); |
| int maxLength; |
| jstring jName; |
| if (isTextField) { |
| maxLength = cachedFocusNode->maxLength(); |
| const WebCore::String& name = cachedFocusNode->name(); |
| jName = env->NewString((jchar*)name.characters(), name.length()); |
| } else { |
| maxLength = -1; |
| jName = 0; |
| } |
| WebCore::IntRect bounds = cachedFocusNode->bounds(); |
| WebCore::String value = cachedFocusNode->getExport(); |
| jstring val = !value.isEmpty() ? env->NewString((jchar *)value.characters(), value.length()) : 0; |
| env->CallVoidMethod(focusnode, m_javaGlue.m_setAll, isTextField, isTextArea, cachedFocusNode->isPassword(), |
| cachedFocusNode->isAnchor(), cachedFocusNode->isRtlText(), maxLength, cachedFocusNode->textSize(), |
| bounds.x(), bounds.y(), bounds.right(), bounds.bottom(), (int)(cachedFocusNode->nodePointer()), |
| (int)(cachedFrame->framePointer()), val, jName, root->textGeneration()); |
| env->DeleteLocalRef(val); |
| env->DeleteLocalRef(focusnode); |
| if (isTextField) |
| env->DeleteLocalRef(jName); |
| return true; |
| } |
| |
| void updateTextEntry() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_updateTextEntry); |
| checkException(env); |
| } |
| |
| void displaySoftKeyboard() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), |
| m_javaGlue.m_displaySoftKeyboard); |
| checkException(env); |
| } |
| |
| void viewInvalidate() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_viewInvalidate); |
| checkException(env); |
| } |
| |
| void viewInvalidateRect(int l, int t, int r, int b) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_viewInvalidateRect, l, r, t, b); |
| checkException(env); |
| } |
| |
| void postInvalidateDelayed(int64_t delay, const WebCore::IntRect& bounds) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_postInvalidateDelayed, |
| delay, bounds.x(), bounds.y(), bounds.right(), bounds.bottom()); |
| checkException(env); |
| } |
| |
| private: // local state for WebView |
| // private to getFrameCache(); other functions operate in a different thread |
| CachedRoot* m_frameCacheUI; // navigation data ready for use |
| FocusReplay m_replay; |
| WebViewCore* m_viewImpl; |
| WebCore::Node* m_invalidNode; |
| int m_generation; // associate unique ID with sent kit focus to match with ui |
| SkPicture* m_navPictureUI; |
| bool m_followedLink; |
| SkMSec m_ringAnimationEnd; |
| // Corresponds to the same-named boolean on the java side. |
| bool m_heightCanMeasure; |
| int m_lastDx; |
| SkMSec m_lastDxTime; |
| WTF::Vector<MatchInfo>* m_matches; |
| // Stores the location of the current match. |
| SkIPoint m_currentMatchLocation; |
| // Tells whether the value in m_currentMatchLocation is valid. |
| bool m_hasCurrentLocation; |
| // Tells whether we have done the setup to draw the Find matches. |
| bool m_isFindPaintSetUp; |
| // Paint used to draw our Find matches. |
| SkPaint m_findPaint; |
| // Paint used for the background of our Find matches. |
| SkPaint m_findBlurPaint; |
| unsigned m_findIndex; |
| }; // end of WebView class |
| |
| /* |
| * Native JNI methods |
| */ |
| static jstring WebCoreStringToJString(JNIEnv *env, WebCore::String string) |
| { |
| int length = string.length(); |
| if (!length) |
| return 0; |
| jstring ret = env->NewString((jchar *)string.characters(), length); |
| env->DeleteLocalRef(ret); |
| return ret; |
| } |
| |
| static void nativeClearFocus(JNIEnv *env, jobject obj, int x, int y) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->clearFocus(x, y, true); |
| } |
| |
| static void nativeCreate(JNIEnv *env, jobject obj, int viewImpl) |
| { |
| WebView* webview = new WebView(env, obj, viewImpl); |
| // NEED THIS OR SOMETHING LIKE IT! |
| //Release(obj); |
| } |
| |
| static void nativeDebugDump(JNIEnv *env, jobject obj) |
| { |
| #if DUMP_NAV_CACHE |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->debugDump(); |
| #endif |
| } |
| |
| static void nativeDrawMatches(JNIEnv *env, jobject obj, jobject canv) |
| { |
| SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); |
| if (!canv) { |
| DBG_NAV_LOG("!canv"); |
| return; |
| } |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| if (!view) { |
| DBG_NAV_LOG("!view"); |
| return; |
| } |
| view->drawMatches(canvas); |
| } |
| |
| static void nativeDrawFocusRing(JNIEnv *env, jobject obj, |
| jobject canv) |
| { |
| SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); |
| if (!canv) { |
| DBG_NAV_LOG("!canv"); |
| return; |
| } |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| if (!view) { |
| DBG_NAV_LOG("!view"); |
| return; |
| } |
| view->drawFocusRing(canvas); |
| } |
| |
| static void nativeDrawSelection(JNIEnv *env, jobject obj, |
| jobject canv, jint x, jint y, bool ex) |
| { |
| SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); |
| if (!canv) { |
| DBG_NAV_LOG("!canv"); |
| return; |
| } |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| if (!view) { |
| DBG_NAV_LOG("!view"); |
| return; |
| } |
| view->drawSelection(canvas, x, y, ex); |
| } |
| |
| static void nativeDrawSelectionRegion(JNIEnv *env, jobject obj, jobject canv) |
| { |
| SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); |
| if (!canv) { |
| DBG_NAV_LOG("!canv"); |
| return; |
| } |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| if (!view) { |
| DBG_NAV_LOG("!view"); |
| return; |
| } |
| view->drawSelectionRegion(canvas); |
| } |
| |
| static jobject nativeImageURI(JNIEnv *env, jobject obj, jint x, jint y) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| WebCore::String uri = view->imageURI(x, y); |
| jstring ret = 0; |
| unsigned len = uri.length(); |
| if (len) { |
| ret = env->NewString((jchar*) uri.characters(), len); |
| env->DeleteLocalRef(ret); |
| } |
| return ret; |
| } |
| |
| static bool nativeFocusNodeWantsKeyEvents(JNIEnv* env, jobject jwebview) { |
| WebView* view = GET_NATIVE_VIEW(env, jwebview); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| return view->focusNodeWantsKeyEvents(); |
| } |
| |
| static void nativeInstrumentReport(JNIEnv *env, jobject obj) |
| { |
| #ifdef ANDROID_INSTRUMENT |
| TimeCounter::reportNow(); |
| #endif |
| } |
| |
| static WebCore::IntRect jrect_to_webrect(JNIEnv* env, jobject obj) |
| { |
| int L, T, R, B; |
| GraphicsJNI::get_jrect(env, obj, &L, &T, &R, &B); |
| return WebCore::IntRect(L, T, R - L, B - T); |
| } |
| |
| static void nativeSelectBestAt(JNIEnv *env, jobject obj, jobject jrect) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| WebCore::IntRect rect = jrect_to_webrect(env, jrect); |
| view->selectBestAt(rect); |
| } |
| |
| static void nativeMarkNodeInvalid(JNIEnv *env, jobject obj, int node) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->markNodeInvalid((WebCore::Node*) node); |
| } |
| |
| static bool nativeMotionUp(JNIEnv *env, jobject obj, |
| int x, int y, int slop, bool isClick) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| return view->motionUp(x, y, slop, isClick, true, false); |
| } |
| |
| static bool nativeUpdateFocusNode(JNIEnv *env, jobject obj) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| return view->updateFocusNode(env); |
| } |
| |
| static bool nativeMoveFocus(JNIEnv *env, jobject obj, |
| int key, int count, bool ignoreScroll) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| DBG_NAV_LOGD("env=%p obj=%p view=%p", env, obj, view); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| return view->moveFocus(key, count, ignoreScroll, true, 0, 0); |
| } |
| |
| static void nativeNotifyFocusSet(JNIEnv *env, jobject obj, bool inEditingMode) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->notifyFocusSet((WebView::FrameCachePermission) inEditingMode); |
| } |
| |
| static void nativeRecomputeFocus(JNIEnv *env, jobject obj) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->recomputeFocus(); |
| } |
| |
| static void nativeRecordButtons(JNIEnv* env, jobject obj, bool hasFocus, |
| bool pressed, bool invalidate) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->nativeRecordButtons(hasFocus, pressed, invalidate); |
| } |
| |
| static void nativeResetFocus(JNIEnv *env, jobject obj) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->resetFocus(); |
| } |
| |
| static void nativeSetFindIsDown(JNIEnv *env, jobject obj) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->setFindIsUp(false); |
| } |
| |
| static void nativeSetFollowedLink(JNIEnv *env, jobject obj, bool followed) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->setFollowedLink(followed); |
| } |
| |
| static void nativeSetHeightCanMeasure(JNIEnv *env, jobject obj, bool measure) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in nativeSetHeightCanMeasure"); |
| view->setHeightCanMeasure(measure); |
| } |
| |
| static jobject nativeGetFocusRingBounds(JNIEnv *env, jobject obj) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| jclass rectClass = env->FindClass("android/graphics/Rect"); |
| LOG_ASSERT(rectClass, "Could not find Rect class!"); |
| jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); |
| LOG_ASSERT(init, "Could not find constructor for Rect"); |
| WebCore::IntRect webRect; |
| view->focusRingBounds(&webRect); |
| jobject rect = env->NewObject(rectClass, init, webRect.x(), |
| webRect.y(), webRect.right(), webRect.bottom()); |
| return rect; |
| } |
| |
| static jobject nativeGetNavBounds(JNIEnv *env, jobject obj) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| jclass rectClass = env->FindClass("android/graphics/Rect"); |
| LOG_ASSERT(rectClass, "Could not find Rect class!"); |
| jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); |
| LOG_ASSERT(init, "Could not find constructor for Rect"); |
| WebCore::IntRect webRect = view->getNavBounds(); |
| jobject rect = env->NewObject(rectClass, init, webRect.x(), |
| webRect.y(), webRect.right(), webRect.bottom()); |
| return rect; |
| } |
| |
| static void nativeSetNavBounds(JNIEnv *env, jobject obj, jobject jrect) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| WebCore::IntRect rect = jrect_to_webrect(env, jrect); |
| view->setNavBounds(rect); |
| } |
| |
| static int nativeFindAll(JNIEnv *env, jobject obj, jstring findLower, |
| jstring findUpper) |
| { |
| // If one or the other is null, do not search. |
| if (!(findLower && findUpper)) |
| return 0; |
| // Obtain the characters for both the lower case string and the upper case |
| // string representing the same word. |
| const jchar* findLowerChars = env->GetStringChars(findLower, 0); |
| const jchar* findUpperChars = env->GetStringChars(findUpper, 0); |
| // If one or the other is null, do not search. |
| if (!(findLowerChars && findUpperChars)) { |
| if (findLowerChars) |
| env->ReleaseStringChars(findLower, findLowerChars); |
| if (findUpperChars) |
| env->ReleaseStringChars(findUpper, findUpperChars); |
| checkException(env); |
| return 0; |
| } |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in nativeFindAll"); |
| view->setFindIsUp(true); |
| CachedRoot* root = view->getFrameCache(WebView::AllowNewer); |
| if (!root) { |
| env->ReleaseStringChars(findLower, findLowerChars); |
| env->ReleaseStringChars(findUpper, findUpperChars); |
| checkException(env); |
| return 0; |
| } |
| int length = env->GetStringLength(findLower); |
| // If the lengths of the strings do not match, then they are not the same |
| // word, so do not search. |
| if (!length || env->GetStringLength(findUpper) != length) { |
| env->ReleaseStringChars(findLower, findLowerChars); |
| env->ReleaseStringChars(findUpper, findUpperChars); |
| checkException(env); |
| return 0; |
| } |
| |
| int width = root->documentWidth(); |
| int height = root->documentHeight(); |
| // Create a FindCanvas, which allows us to fake draw into it so we can |
| // figure out where our search string is rendered (and how many times). |
| FindCanvas canvas(width, height, (const UChar*) findLowerChars, |
| (const UChar*) findUpperChars, length << 1); |
| SkBitmap bitmap; |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); |
| canvas.setBitmapDevice(bitmap); |
| canvas.drawPicture(*(root->getPicture())); |
| WTF::Vector<MatchInfo>* matches = canvas.detachMatches(); |
| // With setMatches, the WebView takes ownership of matches |
| view->setMatches(matches); |
| |
| env->ReleaseStringChars(findLower, findLowerChars); |
| env->ReleaseStringChars(findUpper, findUpperChars); |
| checkException(env); |
| return canvas.found(); |
| } |
| |
| static void nativeFindNext(JNIEnv *env, jobject obj, bool forward) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in nativeFindNext"); |
| view->findNext(forward); |
| } |
| |
| static void nativeUpdateCachedTextfield(JNIEnv *env, jobject obj, jstring updatedText, jint generation) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in nativeUpdateCachedTextfield"); |
| CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); |
| if (!root) |
| return; |
| const CachedNode* cachedFocusNode = root->currentFocus(); |
| if (!cachedFocusNode || (!cachedFocusNode->isTextField() && !cachedFocusNode->isTextArea())) |
| return; |
| WebCore::String webcoreString = to_string(env, updatedText); |
| (const_cast<CachedNode*>(cachedFocusNode))->setExport(webcoreString); |
| root->setTextGeneration(generation); |
| checkException(env); |
| } |
| |
| static void nativeDestroy(JNIEnv *env, jobject obj) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOGD("nativeDestroy view: %p", view); |
| LOG_ASSERT(view, "view not set in nativeDestroy"); |
| delete view; |
| } |
| |
| static void nativeMoveSelection(JNIEnv *env, jobject obj, int x, int y, bool ex) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| view->moveSelection(x, y, ex); |
| } |
| |
| static jobject nativeGetSelection(JNIEnv *env, jobject obj) |
| { |
| WebView* view = GET_NATIVE_VIEW(env, obj); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| return GraphicsJNI::createRegion(env, new SkRegion(view->getSelection())); |
| } |
| |
| #ifdef ANDROID_DUMP_DISPLAY_TREE |
| static void dumpToFile(const char text[], void* file) { |
| fwrite(text, 1, strlen(text), reinterpret_cast<FILE*>(file)); |
| fwrite("\n", 1, 1, reinterpret_cast<FILE*>(file)); |
| } |
| #endif |
| |
| static void nativeDumpDisplayTree(JNIEnv* env, jobject jwebview, jstring jurl) |
| { |
| #ifdef ANDROID_DUMP_DISPLAY_TREE |
| WebView* view = GET_NATIVE_VIEW(env, jwebview); |
| LOG_ASSERT(view, "view not set in %s", __FUNCTION__); |
| |
| CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); |
| if (root) { |
| SkPicture* picture = root->getPicture(); |
| if (picture) { |
| FILE* file = fopen(DISPLAY_TREE_LOG_FILE, "w"); |
| if (file) { |
| SkFormatDumper dumper(dumpToFile, file); |
| // dump the URL |
| if (jurl) { |
| const char* str = env->GetStringUTFChars(jurl, 0); |
| SkDebugf("Dumping %s to %s\n", str, DISPLAY_TREE_LOG_FILE); |
| dumpToFile(str, file); |
| env->ReleaseStringUTFChars(jurl, str); |
| } |
| // now dump the display tree |
| SkDumpCanvas canvas(&dumper); |
| // this will playback the picture into the canvas, which will |
| // spew its contents to the dumper |
| picture->draw(&canvas); |
| // we're done with the file now |
| fwrite("\n", 1, 1, file); |
| fclose(file); |
| } |
| } |
| } |
| #endif |
| } |
| |
| /* |
| * JNI registration |
| */ |
| static JNINativeMethod gJavaWebViewMethods[] = { |
| { "nativeFindAll", "(Ljava/lang/String;Ljava/lang/String;)I", |
| (void*) nativeFindAll }, |
| { "nativeFindNext", "(Z)V", |
| (void*) nativeFindNext }, |
| { "nativeClearFocus", "(II)V", |
| (void*) nativeClearFocus }, |
| { "nativeCreate", "(I)V", |
| (void*) nativeCreate }, |
| { "nativeDebugDump", "()V", |
| (void*) nativeDebugDump }, |
| { "nativeDestroy", "()V", |
| (void*) nativeDestroy }, |
| { "nativeDrawMatches", "(Landroid/graphics/Canvas;)V", |
| (void*) nativeDrawMatches }, |
| { "nativeDrawFocusRing", "(Landroid/graphics/Canvas;)V", |
| (void*) nativeDrawFocusRing }, |
| { "nativeDrawSelection", "(Landroid/graphics/Canvas;IIZ)V", |
| (void*) nativeDrawSelection }, |
| { "nativeDrawSelectionRegion", "(Landroid/graphics/Canvas;)V", |
| (void*) nativeDrawSelectionRegion }, |
| { "nativeUpdateFocusNode", "()Z", |
| (void*) nativeUpdateFocusNode }, |
| { "nativeGetFocusRingBounds", "()Landroid/graphics/Rect;", |
| (void*) nativeGetFocusRingBounds }, |
| { "nativeGetNavBounds", "()Landroid/graphics/Rect;", |
| (void*) nativeGetNavBounds }, |
| { "nativeInstrumentReport", "()V", |
| (void*) nativeInstrumentReport }, |
| { "nativeMarkNodeInvalid", "(I)V", |
| (void*) nativeMarkNodeInvalid }, |
| { "nativeMotionUp", "(IIIZ)Z", |
| (void*) nativeMotionUp }, |
| { "nativeMoveFocus", "(IIZ)Z", |
| (void*) nativeMoveFocus }, |
| { "nativeNotifyFocusSet", "(Z)V", |
| (void*) nativeNotifyFocusSet }, |
| { "nativeRecomputeFocus", "()V", |
| (void*) nativeRecomputeFocus }, |
| { "nativeRecordButtons", "(ZZZ)V", |
| (void*) nativeRecordButtons }, |
| { "nativeResetFocus", "()V", |
| (void*) nativeResetFocus }, |
| { "nativeSelectBestAt", "(Landroid/graphics/Rect;)V", |
| (void*) nativeSelectBestAt }, |
| { "nativeSetFindIsDown", "()V", |
| (void*) nativeSetFindIsDown }, |
| { "nativeSetFollowedLink", "(Z)V", |
| (void*) nativeSetFollowedLink }, |
| { "nativeSetHeightCanMeasure", "(Z)V", |
| (void*) nativeSetHeightCanMeasure }, |
| { "nativeSetNavBounds", "(Landroid/graphics/Rect;)V", |
| (void*) nativeSetNavBounds }, |
| { "nativeImageURI", "(II)Ljava/lang/String;", |
| (void*) nativeImageURI }, |
| { "nativeFocusNodeWantsKeyEvents", "()Z", |
| (void*)nativeFocusNodeWantsKeyEvents }, |
| { "nativeUpdateCachedTextfield", "(Ljava/lang/String;I)V", |
| (void*) nativeUpdateCachedTextfield }, |
| { "nativeMoveSelection", "(IIZ)V", |
| (void*) nativeMoveSelection }, |
| { "nativeGetSelection", "()Landroid/graphics/Region;", |
| (void*) nativeGetSelection }, |
| { "nativeDumpDisplayTree", "(Ljava/lang/String;)V", |
| (void*) nativeDumpDisplayTree } |
| }; |
| |
| int register_webview(JNIEnv* env) |
| { |
| jclass clazz = env->FindClass("android/webkit/WebView"); |
| LOG_ASSERT(clazz, "Unable to find class android/webkit/WebView"); |
| gWebViewField = env->GetFieldID(clazz, "mNativeClass", "I"); |
| LOG_ASSERT(gWebViewField, "Unable to find android/webkit/WebView.mNativeClass"); |
| |
| return jniRegisterNativeMethods(env, "android/webkit/WebView", gJavaWebViewMethods, NELEM(gJavaWebViewMethods)); |
| } |
| |
| } // namespace android |