/* * 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 #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 GET_NATIVE_VIEW #undef GET_NATIVE_VIEW #endif #define GET_NATIVE_VIEW(env, obj) ((WebView*)env->GetIntField(obj, gWebViewField)) #include #include #include #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_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", "(II)V"); m_javaGlue.m_clearTextEntry = GetJMethod(env, clazz, "clearTextEntry", "()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. void nativeRecordButtons(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 (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& 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); } 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(foundFrame), const_cast(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(frame), const_cast(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(); } void motionUp(int x, int y, int slop, bool isClick, bool inval, bool retry) { 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 } } } 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; } 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(frame), const_cast(result)); bool newNodeIsTextArea = focusIsTextArea(DontAllowNewer); if (result->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 (oldNodeIsTextArea) clearTextEntry(); } } 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* 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); 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* 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 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 void 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__); 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 pressed, bool invalidate) { WebView* view = GET_NATIVE_VIEW(env, obj); LOG_ASSERT(view, "view not set in %s", __FUNCTION__); view->nativeRecordButtons(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, "", "(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, "", "(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* 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(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)); fwrite("\n", 1, 1, reinterpret_cast(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 }, { "nativeMarkNodeInvalid", "(I)V", (void*) nativeMarkNodeInvalid }, { "nativeMotionUp", "(IIIZ)V", (void*) nativeMotionUp }, { "nativeMoveFocus", "(IIZ)Z", (void*) nativeMoveFocus }, { "nativeNotifyFocusSet", "(Z)V", (void*) nativeNotifyFocusSet }, { "nativeRecomputeFocus", "()V", (void*) nativeRecomputeFocus }, { "nativeRecordButtons", "(ZZ)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