/* * 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 THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "CachedPrefix.h" #include "android_graphics.h" #include "CachedHistory.h" #include "CachedInput.h" #include "CachedLayer.h" #include "CachedNode.h" #include "FindCanvas.h" #include "FloatRect.h" #include "LayerAndroid.h" #include "ParseCanvas.h" #include "SkBitmap.h" #include "SkBounder.h" #include "SkPixelRef.h" #include "SkRegion.h" #include "CachedRoot.h" #if DEBUG_NAV_UI #include "wtf/text/CString.h" #endif #define DONT_CENTER_IF_ALREADY_VISIBLE using std::min; using std::max; #ifdef DUMP_NAV_CACHE_USING_PRINTF extern android::Mutex gWriteLogMutex; #endif namespace android { class CommonCheck : public SkBounder { public: enum Type { kNo_Type, kDrawBitmap_Type, kDrawGlyph_Type, kDrawPaint_Type, kDrawPath_Type, kDrawPicture_Type, kDrawPoints_Type, kDrawPosText_Type, kDrawPosTextH_Type, kDrawRect_Type, kDrawSprite_Type, kDrawText_Type, kDrawTextOnPath_Type, kPopLayer_Type, kPushLayer_Type, kPushSave_Type }; static bool isTextType(Type t) { return t == kDrawPosTextH_Type || t == kDrawText_Type; } CommonCheck() : mType(kNo_Type), mAllOpaque(true), mIsOpaque(true) { setEmpty(); } bool doRect(Type type) { mType = type; return doIRect(mUnion); } bool isEmpty() { return mUnion.isEmpty(); } bool joinGlyphs(const SkIRect& rect) { bool isGlyph = mType == kDrawGlyph_Type; if (isGlyph) mUnion.join(rect); return isGlyph; } void setAllOpaque(bool opaque) { mAllOpaque = opaque; } void setEmpty() { mUnion.setEmpty(); } void setIsOpaque(bool opaque) { mIsOpaque = opaque; } void setType(Type type) { mType = type; } Type mType; SkIRect mUnion; bool mAllOpaque; bool mIsOpaque; }; #if DEBUG_NAV_UI static const char* TypeNames[] = { "kNo_Type", "kDrawBitmap_Type", "kDrawGlyph_Type", "kDrawPaint_Type", "kDrawPath_Type", "kDrawPicture_Type", "kDrawPoints_Type", "kDrawPosText_Type", "kDrawPosTextH_Type", "kDrawRect_Type", "kDrawSprite_Type", "kDrawText_Type", "kDrawTextOnPath_Type", "kPopLayer_Type", "kPushLayer_Type", "kPushSave_Type" }; #endif #define kMargin 16 #define kSlop 2 class BoundsCanvas : public ParseCanvas { public: BoundsCanvas(CommonCheck* bounder) : mBounder(*bounder) { mTransparentLayer = 0; setBounder(bounder); } virtual void drawPaint(const SkPaint& paint) { mBounder.setType(CommonCheck::kDrawPaint_Type); INHERITED::drawPaint(paint); } virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) { mBounder.setType(CommonCheck::kDrawPoints_Type); INHERITED::drawPoints(mode, count, pts, paint); } virtual void drawRect(const SkRect& rect, const SkPaint& paint) { mBounder.setType(CommonCheck::kDrawRect_Type); INHERITED::drawRect(rect, paint); } virtual void drawPath(const SkPath& path, const SkPaint& paint) { mBounder.setType(CommonCheck::kDrawPath_Type); INHERITED::drawPath(path, paint); } virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, const SkMatrix& matrix, const SkPaint& paint) { mBounder.setType(CommonCheck::kDrawBitmap_Type); mBounder.setIsOpaque(bitmap.isOpaque()); INHERITED::commonDrawBitmap(bitmap, rect, matrix, paint); } virtual void drawSprite(const SkBitmap& bitmap, int left, int top, const SkPaint* paint) { mBounder.setType(CommonCheck::kDrawSprite_Type); mBounder.setIsOpaque(bitmap.isOpaque() && (!paint || paint->getAlpha() == 255)); INHERITED::drawSprite(bitmap, left, top, paint); } virtual void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, const SkPaint& paint) { mBounder.setEmpty(); mBounder.setType(CommonCheck::kDrawGlyph_Type); INHERITED::drawText(text, byteLength, x, y, paint); mBounder.doRect(CommonCheck::kDrawText_Type); } virtual void drawPosText(const void* text, size_t byteLength, const SkPoint pos[], const SkPaint& paint) { mBounder.setEmpty(); mBounder.setType(CommonCheck::kDrawGlyph_Type); INHERITED::drawPosText(text, byteLength, pos, paint); if (!mBounder.isEmpty()) mBounder.doRect(CommonCheck::kDrawPosText_Type); } virtual void drawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY, const SkPaint& paint) { mBounder.setEmpty(); mBounder.setType(CommonCheck::kDrawGlyph_Type); INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); if (mBounder.mUnion.isEmpty()) { DBG_NAV_LOGD("empty constY=%g", SkScalarToFloat(constY)); return; } SkPaint::FontMetrics metrics; paint.getFontMetrics(&metrics); SkPoint upDown[2] = { {xpos[0], constY + metrics.fAscent}, {xpos[0], constY + metrics.fDescent} }; const SkMatrix& matrix = getTotalMatrix(); matrix.mapPoints(upDown, 2); if (upDown[0].fX == upDown[1].fX) { mBounder.mUnion.fTop = SkScalarFloor(upDown[0].fY); mBounder.mUnion.fBottom = SkScalarFloor(upDown[1].fY); } mBounder.doRect(CommonCheck::kDrawPosTextH_Type); } virtual void drawTextOnPath(const void* text, size_t byteLength, const SkPath& path, const SkMatrix* matrix, const SkPaint& paint) { mBounder.setEmpty(); mBounder.setType(CommonCheck::kDrawGlyph_Type); INHERITED::drawTextOnPath(text, byteLength, path, matrix, paint); mBounder.doRect(CommonCheck::kDrawTextOnPath_Type); } virtual void drawPicture(SkPicture& picture) { mBounder.setType(CommonCheck::kDrawPicture_Type); INHERITED::drawPicture(picture); } virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, SaveFlags flags) { int depth = INHERITED::saveLayer(bounds, paint, flags); if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) { mTransparentLayer = depth; mBounder.setAllOpaque(false); } return depth; } virtual void restore() { mBounder.setType(CommonCheck::kDrawSprite_Type); // for layer draws int depth = getSaveCount(); if (depth == mTransparentLayer) { mTransparentLayer = 0; mBounder.setAllOpaque(true); } INHERITED::restore(); } int mTransparentLayer; CommonCheck& mBounder; private: typedef ParseCanvas INHERITED; }; /* LeftCheck examines the text in a picture, within a viewable rectangle, and returns via left() the position of the left edge of the paragraph. It first looks at the left edge of the test point, then looks above and below it for more lines of text to determine the div's left edge. */ class LeftCheck : public CommonCheck { public: LeftCheck(int x, int y) : mX(x), mY(y), mHitLeft(INT_MAX), mMostLeft(INT_MAX) { mHit.set(x - (HIT_SLOP << 1), y - HIT_SLOP, x, y + HIT_SLOP); mPartial.setEmpty(); mBounds.setEmpty(); mPartialType = kNo_Type; } int left() { if (isTextType(mType)) doRect(); // process the final line of text return mMostLeft != INT_MAX ? mMostLeft : mX >> 1; } // FIXME: this is identical to CenterCheck::onIRect() // refactor so that LeftCheck and CenterCheck inherit common functions virtual bool onIRect(const SkIRect& rect) { bool opaqueBitmap = mType == kDrawBitmap_Type && mIsOpaque; if (opaqueBitmap && rect.contains(mX, mY)) { mMostLeft = rect.fLeft; return false; } if (joinGlyphs(rect)) // assembles glyphs into a text string return false; if (!isTextType(mType) && !opaqueBitmap) return false; /* Text on one line may be broken into several parts. Reassemble the text into a rectangle before considering it. */ if (rect.fTop < mPartial.fBottom && rect.fBottom > mPartial.fTop && mPartial.fRight + JOIN_SLOP_X >= rect.fLeft && (mPartialType != kDrawBitmap_Type || mPartial.height() <= rect.height() + JOIN_SLOP_Y)) { DBG_NAV_LOGD("LeftCheck join mPartial=(%d, %d, %d, %d)" " rect=(%d, %d, %d, %d)", mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); mPartial.join(rect); return false; } if (mPartial.isEmpty() == false) { doRect(); // process the previous line of text #if DEBUG_NAV_UI if (mHitLeft == INT_MAX) DBG_NAV_LOGD("LeftCheck disabled rect=(%d, %d, %d, %d)", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); #endif } mPartial = rect; mPartialType = mType; return false; } void doRect() { /* Record the outer bounds of the lines of text that intersect the touch coordinates, given some slop */ if (SkIRect::Intersects(mPartial, mHit)) { if (mHitLeft > mPartial.fLeft) mHitLeft = mPartial.fLeft; DBG_NAV_LOGD("LeftCheck mHitLeft=%d", mHitLeft); } else if (mHitLeft == INT_MAX) return; // wait for intersect success /* If text is too far away vertically, don't consider it */ if (!mBounds.isEmpty() && (mPartial.fTop > mBounds.fBottom + HIT_SLOP || mPartial.fBottom < mBounds.fTop - HIT_SLOP)) { DBG_NAV_LOGD("LeftCheck stop mPartial=(%d, %d, %d, %d)" " mBounds=(%d, %d, %d, %d)", mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, mBounds.fLeft, mBounds.fTop, mBounds.fRight, mBounds.fBottom); mHitLeft = INT_MAX; // and disable future comparisons return; } /* If the considered text is completely to the left or right of the touch coordinates, skip it, turn off further detection */ if (mPartial.fLeft > mX || mPartial.fRight < mX) { DBG_NAV_LOGD("LeftCheck stop mX=%d mPartial=(%d, %d, %d, %d)", mX, mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom); mHitLeft = INT_MAX; return; } /* record the smallest margins on the left and right */ if (mMostLeft > mPartial.fLeft) { DBG_NAV_LOGD("LeftCheck new mMostLeft=%d (old=%d)", mPartial.fLeft, mMostLeft); mMostLeft = mPartial.fLeft; } if (mBounds.isEmpty()) mBounds = mPartial; else if (mPartial.fBottom > mBounds.fBottom) { DBG_NAV_LOGD("LeftCheck new bottom=%d (old=%d)", mPartial.fBottom, mBounds.fBottom); mBounds.fBottom = mPartial.fBottom; } } static const int JOIN_SLOP_X = 30; // horizontal space between text parts static const int JOIN_SLOP_Y = 5; // vertical space between text lines static const int HIT_SLOP = 30; // diameter allowing for tap size /* const */ SkIRect mHit; // sloppy hit rectangle SkIRect mBounds; // reference bounds SkIRect mPartial; // accumulated text bounds, per line const int mX; // touch location const int mY; int mHitLeft; // touched text extremes int mMostLeft; // paragraph extremes Type mPartialType; }; /* CenterCheck examines the text in a picture, within a viewable rectangle, and returns via center() the optimal amount to scroll in x to display the paragraph of text. The caller of CenterCheck has configured (but not allocated) a bitmap the height and three times the width of the view. The picture is drawn centered in the bitmap, so text that would be revealed, if the view was scrolled up to a view-width to the left or right, is considered. */ class CenterCheck : public CommonCheck { public: CenterCheck(int x, int y, int width) : mX(x), mY(y), mHitLeft(x), mHitRight(x), mMostLeft(INT_MAX), mMostRight(-INT_MAX), mViewLeft(width), mViewRight(width << 1) { mHit.set(x - CENTER_SLOP, y - CENTER_SLOP, x + CENTER_SLOP, y + CENTER_SLOP); mPartial.setEmpty(); } int center() { doRect(); // process the final line of text /* If the touch coordinates aren't near any text, return 0 */ if (mHitLeft == mHitRight) { DBG_NAV_LOGD("abort: mHitLeft=%d ==mHitRight", mHitLeft); return 0; } int leftOver = mHitLeft - mViewLeft; int rightOver = mHitRight - mViewRight; int center; /* If the touched text is too large to entirely fit on the screen, center it. */ if (leftOver < 0 && rightOver > 0) { center = (leftOver + rightOver) >> 1; DBG_NAV_LOGD("overlap: leftOver=%d rightOver=%d center=%d", leftOver, rightOver, center); return center; } center = (mMostLeft + mMostRight) >> 1; // the paragraph center if (leftOver > 0 && rightOver >= 0) { // off to the right if (center > mMostLeft) // move to center loses left-most text? center = mMostLeft; } else if (rightOver < 0 && leftOver <= 0) { // off to the left if (center < mMostRight) // move to center loses right-most text? center = mMostRight; } else { #ifdef DONT_CENTER_IF_ALREADY_VISIBLE center = 0; // paragraph is already fully visible #endif } DBG_NAV_LOGD("scroll: leftOver=%d rightOver=%d center=%d", leftOver, rightOver, center); return center; } protected: virtual bool onIRect(const SkIRect& rect) { if (joinGlyphs(rect)) // assembles glyphs into a text string return false; if (!isTextType(mType)) return false; /* Text on one line may be broken into several parts. Reassemble the text into a rectangle before considering it. */ if (rect.fTop < mPartial.fBottom && rect.fBottom > mPartial.fTop && mPartial.fRight + CENTER_SLOP >= rect.fLeft) { DBG_NAV_LOGD("join mPartial=(%d, %d, %d, %d) rect=(%d, %d, %d, %d)", mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); mPartial.join(rect); return false; } if (mPartial.isEmpty() == false) doRect(); // process the previous line of text mPartial = rect; return false; } void doRect() { /* Record the outer bounds of the lines of text that was 'hit' by the touch coordinates, given some slop */ if (SkIRect::Intersects(mPartial, mHit)) { if (mHitLeft > mPartial.fLeft) mHitLeft = mPartial.fLeft; if (mHitRight < mPartial.fRight) mHitRight = mPartial.fRight; DBG_NAV_LOGD("mHitLeft=%d mHitRight=%d", mHitLeft, mHitRight); } /* If the considered text is completely to the left or right of the touch coordinates, skip it */ if (mPartial.fLeft > mX || mPartial.fRight < mX) return; int leftOver = mPartial.fLeft - mViewLeft; int rightOver = mPartial.fRight - mViewRight; /* If leftOver <= 0, the text starts off the screen. If rightOver >= 0, the text ends off the screen. */ if (leftOver <= 0 && rightOver >= 0) // discard wider than screen return; #ifdef DONT_CENTER_IF_ALREADY_VISIBLE if (leftOver > 0 && rightOver < 0) // discard already visible return; #endif /* record the smallest margins on the left and right */ if (mMostLeft > leftOver) mMostLeft = leftOver; if (mMostRight < rightOver) mMostRight = rightOver; DBG_NAV_LOGD("leftOver=%d rightOver=%d mMostLeft=%d mMostRight=%d", leftOver, rightOver, mMostLeft, mMostRight); } static const int CENTER_SLOP = 10; // space between text parts and lines /* const */ SkIRect mHit; // sloppy hit rectangle SkIRect mPartial; // accumulated text bounds, per line const int mX; // touch location const int mY; int mHitLeft; // touched text extremes int mHitRight; int mMostLeft; // paragraph extremes int mMostRight; const int mViewLeft; // middle third of 3x-wide view const int mViewRight; }; class ImageCanvas : public ParseCanvas { public: ImageCanvas(SkBounder* bounder) : mURI(NULL) { setBounder(bounder); } const char* getURI() { return mURI; } protected: // Currently webkit's bitmap draws always seem to be cull'd before this entry // point is called, so we assume that any bitmap that gets here is inside our // tiny clip (may not be true in the future) virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, const SkMatrix& , const SkPaint& ) { SkPixelRef* pixelRef = bitmap.pixelRef(); if (pixelRef != NULL) { mURI = pixelRef->getURI(); } } private: const char* mURI; }; class ImageCheck : public SkBounder { public: virtual bool onIRect(const SkIRect& rect) { return false; } }; class JiggleCheck : public CommonCheck { public: JiggleCheck(int delta, int width) : mDelta(delta), mMaxX(width) { mMaxJiggle = 0; mMinX = mMinJiggle = abs(delta); mMaxWidth = width + mMinX; } int jiggle() { if (mMinJiggle > mMaxJiggle) return mDelta; int avg = (mMinJiggle + mMaxJiggle + 1) >> 1; return mDelta < 0 ? -avg : avg; } virtual bool onIRect(const SkIRect& rect) { if (joinGlyphs(rect)) return false; if (mType != kDrawBitmap_Type && !isTextType(mType)) return false; int min, max; if (mDelta < 0) { min = mMinX - rect.fLeft; max = mMaxWidth - rect.fRight; } else { min = rect.fRight - mMaxX; max = rect.fLeft; } if (min <= 0) return false; if (max >= mMinX) return false; if (mMinJiggle > min) mMinJiggle = min; if (mMaxJiggle < max) mMaxJiggle = max; return false; } int mDelta; int mMaxJiggle; int mMaxX; int mMinJiggle; int mMinX; int mMaxWidth; }; class RingCheck : public CommonCheck { public: RingCheck(const WTF::Vector& rings, const WebCore::IntRect& bitBounds, const WebCore::IntRect& testBounds, bool singleImage) : mTestBounds(testBounds) , mBitBounds(bitBounds) , mPushPop(false) , mSingleImage(singleImage) { const WebCore::IntRect* r; for (r = rings.begin(); r != rings.end(); r++) { SkIRect fatter = {r->x(), r->y(), r->right(), r->bottom()}; fatter.inset(-CURSOR_RING_HIT_TEST_RADIUS, -CURSOR_RING_HIT_TEST_RADIUS); DBG_NAV_LOGD("RingCheck fat=(%d,%d,r=%d,b=%d)", fatter.fLeft, fatter.fTop, fatter.fRight, fatter.fBottom); mTextSlop.op(fatter, SkRegion::kUnion_Op); mTextTest.op(*r, SkRegion::kUnion_Op); } int dx = -bitBounds.x(); int dy = -bitBounds.y(); DBG_NAV_LOGD("RingCheck translate=(%d,%d)", dx, dy); mTextSlop.translate(dx, dy); mTextTest.translate(dx, dy); mTestBounds.translate(dx, dy); mEmpty.setEmpty(); } bool hiddenRings(SkRegion* clipped) { findBestLayer(); if (!mBestLayer) { DBG_NAV_LOG("RingCheck empty"); clipped->setEmpty(); return true; } const SkRegion* layersEnd = mLayers.end(); const Type* layerTypes = &mLayerTypes[mBestLayer - mLayers.begin()]; bool collectGlyphs = true; bool collectOvers = false; SkRegion over; for (const SkRegion* layers = mBestLayer; layers != layersEnd; layers++) { Type layerType = *layerTypes++; DBG_NAV_LOGD("RingCheck #%d %s (%d,%d,r=%d,b=%d)", layers - mLayers.begin(), TypeNames[layerType], layers->getBounds().fLeft, layers->getBounds().fTop, layers->getBounds().fRight, layers->getBounds().fBottom); if (collectGlyphs && (layerType == kDrawGlyph_Type || ((layerType == kDrawRect_Type && mTextTest.contains(*layers)) || (layerType == kDrawBitmap_Type && mTextSlop.contains(*layers))))) { DBG_NAV_LOGD("RingCheck #%d collectOvers", layers - mLayers.begin()); collectOvers = true; clipped->op(*layers, SkRegion::kUnion_Op); continue; } collectGlyphs &= layerType != kPushLayer_Type; if (collectOvers && (layerType == kDrawRect_Type || layerType == kDrawBitmap_Type || (!collectGlyphs && layerType == kDrawSprite_Type))) { DBG_NAV_LOGD("RingCheck #%d over.op", layers - mLayers.begin()); over.op(*layers, SkRegion::kUnion_Op); } } bool result = !collectOvers || clipped->intersects(over); const SkIRect t = clipped->getBounds(); const SkIRect o = over.getBounds(); clipped->op(over, SkRegion::kDifference_Op); clipped->translate(mBitBounds.x(), mBitBounds.y()); const SkIRect c = clipped->getBounds(); DBG_NAV_LOGD("RingCheck intersects=%s text=(%d,%d,r=%d,b=%d)" " over=(%d,%d,r=%d,b=%d) clipped=(%d,%d,r=%d,b=%d)", result ? "true" : "false", t.fLeft, t.fTop, t.fRight, t.fBottom, o.fLeft, o.fTop, o.fRight, o.fBottom, c.fLeft, c.fTop, c.fRight, c.fBottom); return result; } void push(Type type, const SkIRect& bounds) { #if DEBUG_NAV_UI // this caches the push string and subquently ignores if pushSave // is immediately followed by popLayer. Push/pop pairs happen // frequently and just add noise to the log. static String lastLog; String currentLog = String("RingCheck append #") + String::number(mLayers.size()) + " type=" + TypeNames[type] + " bounds=(" + String::number(bounds.fLeft) + "," + String::number(bounds.fTop) + "," + String::number(bounds.fRight) + "," + String::number(bounds.fBottom) + ")"; if (lastLog.length() == 0 || type != kPopLayer_Type) { if (lastLog.length() != 0) DBG_NAV_LOGD("%s", lastLog.latin1().data()); if (type == kPushSave_Type) lastLog = currentLog; else DBG_NAV_LOGD("%s", currentLog.latin1().data()); } else lastLog = ""; #endif popEmpty(); mPushPop |= type >= kPopLayer_Type; if (type == kPopLayer_Type) { Type last = mLayerTypes.last(); // remove empty brackets if (last == kPushLayer_Type || last == kPushSave_Type) { mLayers.removeLast(); mLayerTypes.removeLast(); return; } // remove push/pop from push/bitmap/pop size_t pushIndex = mLayerTypes.size() - 2; if (last == kDrawBitmap_Type && mLayerTypes.at(pushIndex) == kPushLayer_Type) { mLayers.at(pushIndex) = mLayers.last(); mLayerTypes.at(pushIndex) = kDrawBitmap_Type; mLayers.removeLast(); mLayerTypes.removeLast(); return; } // remove non-layer brackets int stack = 0; Type* types = mLayerTypes.end(); while (types != mLayerTypes.begin()) { Type type = *--types; if (type == kPopLayer_Type) { stack++; continue; } if (type != kPushLayer_Type && type != kPushSave_Type) continue; if (--stack >= 0) continue; if (type == kPushLayer_Type) break; int remove = types - mLayerTypes.begin(); DBG_NAV_LOGD("RingCheck remove=%d mLayers.size=%d" " mLayerTypes.size=%d", remove, mLayers.size(), mLayerTypes.size()); mLayers.remove(remove); mLayerTypes.remove(remove); mAppendLikeTypes = false; return; } } mLayers.append(bounds); mLayerTypes.append(type); } void startText(const SkPaint& paint) { mPaint = &paint; if (!mLayerTypes.isEmpty() && mLayerTypes.last() == kDrawGlyph_Type && !mLayers.last().isEmpty()) { push(kDrawGlyph_Type, mEmpty); } } bool textOutsideRings() { findBestLayer(); if (!mBestLayer) { DBG_NAV_LOG("RingCheck empty"); return false; } const SkRegion* layers = mBestLayer; const Type* layerTypes = &mLayerTypes[layers - mLayers.begin()]; // back up to include text drawn before the best layer SkRegion active = SkRegion(mBitBounds); active.translate(-mBitBounds.x(), -mBitBounds.y()); while (layers != mLayers.begin()) { --layers; Type layerType = *--layerTypes; DBG_NAV_LOGD("RingCheck #%d %s" " mTestBounds=(%d,%d,r=%d,b=%d) layers=(%d,%d,r=%d,b=%d)" " active=(%d,%d,r=%d,b=%d)", layers - mLayers.begin(), TypeNames[layerType], mTestBounds.getBounds().fLeft, mTestBounds.getBounds().fTop, mTestBounds.getBounds().fRight, mTestBounds.getBounds().fBottom, layers->getBounds().fLeft, layers->getBounds().fTop, layers->getBounds().fRight, layers->getBounds().fBottom, active.getBounds().fLeft, active.getBounds().fTop, active.getBounds().fRight, active.getBounds().fBottom); if (layerType == kDrawRect_Type || layerType == kDrawBitmap_Type) { SkRegion temp = *layers; temp.op(mTestBounds, SkRegion::kIntersect_Op); active.op(temp, SkRegion::kDifference_Op); if (active.isEmpty()) { DBG_NAV_LOGD("RingCheck #%d empty", layers - mLayers.begin()); break; } } else if (layerType == kDrawGlyph_Type) { SkRegion temp = *layers; temp.op(active, SkRegion::kIntersect_Op); if (!mTestBounds.intersects(temp)) continue; if (!mTestBounds.contains(temp)) return false; } else break; } layers = mBestLayer; layerTypes = &mLayerTypes[layers - mLayers.begin()]; bool foundGlyph = false; bool collectGlyphs = true; do { Type layerType = *layerTypes++; DBG_NAV_LOGD("RingCheck #%d %s mTestBounds=(%d,%d,r=%d,b=%d)" " layers=(%d,%d,r=%d,b=%d) collects=%s intersects=%s contains=%s", layers - mLayers.begin(), TypeNames[layerType], mTestBounds.getBounds().fLeft, mTestBounds.getBounds().fTop, mTestBounds.getBounds().fRight, mTestBounds.getBounds().fBottom, layers->getBounds().fLeft, layers->getBounds().fTop, layers->getBounds().fRight, layers->getBounds().fBottom, collectGlyphs ? "true" : "false", mTestBounds.intersects(*layers) ? "true" : "false", mTextSlop.contains(*layers) ? "true" : "false"); if (collectGlyphs && layerType == kDrawGlyph_Type) { if (!mTestBounds.intersects(*layers)) continue; if (!mTextSlop.contains(*layers)) return false; foundGlyph = true; } collectGlyphs &= layerType != kPushLayer_Type; } while (++layers != mLayers.end()); DBG_NAV_LOGD("RingCheck foundGlyph=%s", foundGlyph ? "true" : "false"); return foundGlyph; } protected: virtual bool onIRect(const SkIRect& rect) { joinGlyphs(rect); if (mType != kDrawGlyph_Type && mType != kDrawRect_Type && mType != kDrawSprite_Type && mType != kDrawBitmap_Type) return false; if (mLayerTypes.isEmpty() || mLayerTypes.last() != mType || !mAppendLikeTypes || mPushPop || mSingleImage // if the last and current were not glyphs, // and the two bounds have a gap between, don't join them -- push // an empty between them || (mType != kDrawGlyph_Type && !joinable(rect))) { push(mType, mEmpty); } DBG_NAV_LOGD("RingCheck join %s (%d,%d,r=%d,b=%d) '%c'", TypeNames[mType], rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, mCh); mLayers.last().op(rect, SkRegion::kUnion_Op); mAppendLikeTypes = true; mPushPop = false; return false; } virtual bool onIRectGlyph(const SkIRect& rect, const SkBounder::GlyphRec& rec) { mCh = ' '; if (mPaint) { SkUnichar unichar; SkPaint utfPaint = *mPaint; utfPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); utfPaint.glyphsToUnichars(&rec.fGlyphID, 1, &unichar); mCh = unichar < 0x7f ? unichar : '?'; } return onIRect(rect); } private: int calcOverlap(SkRegion& testRegion) { if (testRegion.isEmpty()) return INT_MAX; testRegion.op(mTextTest, SkRegion::kXOR_Op); SkRegion::Iterator iter(testRegion); int area = 0; while (!iter.done()) { const SkIRect& cr = iter.rect(); area += cr.width() * cr.height(); iter.next(); } DBG_NAV_LOGD("RingCheck area=%d", area); return area; } void findBestLayer() { popEmpty(); mBestLayer = 0; const SkRegion* layers = mLayers.begin(); const SkRegion* layersEnd = mLayers.end(); if (layers == layersEnd) { DBG_NAV_LOG("RingCheck empty"); return; } // find text most like focus rings by xoring found with original int bestArea = INT_MAX; const SkRegion* testLayer = 0; SkRegion testRegion; const Type* layerTypes = &mLayerTypes[layers - mLayers.begin()]; for (; layers != mLayers.end(); layers++) { Type layerType = *layerTypes++; #if DEBUG_NAV_UI const SkIRect& gb = layers->getBounds(); const SkIRect& tb = mTextSlop.getBounds(); DBG_NAV_LOGD("RingCheck #%d %s mTextSlop=(%d,%d,%d,%d)" " contains=%s bounds=(%d,%d,%d,%d)", layers - mLayers.begin(), TypeNames[layerType], tb.fLeft, tb.fTop, tb.fRight, tb.fBottom, mTextSlop.contains(*layers) ? "true" : "false", gb.fLeft, gb.fTop, gb.fRight, gb.fBottom); #endif if (((layerType == kDrawGlyph_Type || layerType == kDrawBitmap_Type) && mTextSlop.contains(*layers)) || (layerType == kDrawRect_Type && mTextTest.contains(*layers))) { if (!testLayer) testLayer = layers; testRegion.op(*layers, SkRegion::kUnion_Op); continue; } if (testLayer) { int area = calcOverlap(testRegion); if (bestArea > area) { bestArea = area; mBestLayer = testLayer; } DBG_NAV_LOGD("RingCheck #%d push test=%d best=%d", layers - mLayers.begin(), testLayer - mLayers.begin(), mBestLayer ? mBestLayer - mLayers.begin() : -1); testRegion.setEmpty(); testLayer = 0; } } if (testLayer && bestArea > calcOverlap(testRegion)) { DBG_NAV_LOGD("RingCheck last best=%d", testLayer - mLayers.begin()); mBestLayer = testLayer; } } bool joinable(const SkIRect& rect) { SkRegion region = mLayers.last(); if (!region.isRect()) return false; const SkIRect& bounds1 = region.getBounds(); int area1 = bounds1.width() * bounds1.height(); area1 += rect.width() * rect.height(); region.op(rect, SkRegion::kUnion_Op); const SkIRect& bounds2 = region.getBounds(); int area2 = bounds2.width() * bounds2.height(); return area2 <= area1; } void popEmpty() { if (mLayerTypes.size() == 0) return; Type last = mLayerTypes.last(); if (last >= kPopLayer_Type) return; const SkRegion& area = mLayers.last(); if (!area.isEmpty()) return; DBG_NAV_LOGD("RingCheck #%d %s", mLayers.size() - 1, TypeNames[last]); mLayers.removeLast(); mLayerTypes.removeLast(); } SkRegion mTestBounds; IntRect mBitBounds; SkIRect mEmpty; const SkRegion* mBestLayer; SkRegion mTextSlop; // outset rects for inclusion test SkRegion mTextTest; // exact rects for xor area test Type mLastType; Vector mLayers; Vector mLayerTypes; const SkPaint* mPaint; char mCh; bool mAppendLikeTypes; bool mPushPop; bool mSingleImage; }; class RingCanvas : public BoundsCanvas { public: RingCanvas(RingCheck* bounder) : INHERITED(bounder) { } protected: virtual void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, const SkPaint& paint) { static_cast(mBounder).startText(paint); INHERITED::drawText(text, byteLength, x, y, paint); } virtual void drawPosText(const void* text, size_t byteLength, const SkPoint pos[], const SkPaint& paint) { static_cast(mBounder).startText(paint); INHERITED::drawPosText(text, byteLength, pos, paint); } virtual void drawTextOnPath(const void* text, size_t byteLength, const SkPath& path, const SkMatrix* matrix, const SkPaint& paint) { static_cast(mBounder).startText(paint); INHERITED::drawTextOnPath(text, byteLength, path, matrix, paint); } virtual void drawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY, const SkPaint& paint) { static_cast(mBounder).startText(paint); INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); } virtual int save(SaveFlags flags) { RingCheck& bounder = static_cast(mBounder); bounder.push(CommonCheck::kPushSave_Type, getTotalClip().getBounds()); return INHERITED::save(flags); } virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, SaveFlags flags) { RingCheck& bounder = static_cast(mBounder); bounder.push(CommonCheck::kPushLayer_Type, getTotalClip().getBounds()); return INHERITED::save(flags); } virtual void restore() { RingCheck& bounder = static_cast(mBounder); bounder.push(CommonCheck::kPopLayer_Type, getTotalClip().getBounds()); INHERITED::restore(); } private: typedef BoundsCanvas INHERITED; }; bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction direction, WebCore::IntPoint* scrollPtr, bool findClosest) { WebCore::IntRect newOutset; const CachedNode* newNode = best->mNode; // see if there's a middle node // if the middle node is in the visited list, // or if none was computed and the newNode is in the visited list, // treat result as NULL if (newNode != NULL && findClosest) { if (best->bounds().intersects(mHistory->mPriorBounds) == false && checkBetween(best, direction)) newNode = best->mNode; if (findClosest && maskIfHidden(best)) { innerMove(document(), best, direction, scrollPtr, false); return true; } newOutset = newNode->cursorRingBounds(best->mFrame); } int delta; bool newNodeInView = scrollDelta(newOutset, direction, &delta); if (delta && scrollPtr && (newNode == NULL || newNodeInView == false || (best->mNavOutside && best->mWorkingOutside))) *scrollPtr = WebCore::IntPoint(direction & UP_DOWN ? 0 : delta, direction & UP_DOWN ? delta : 0); return false; } void CachedRoot::calcBitBounds(const IntRect& nodeBounds, IntRect* bitBounds) const { IntRect contentBounds = IntRect(0, 0, mPicture->width(), mPicture->height()); IntRect overBounds = nodeBounds; overBounds.inflate(kMargin); IntRect viewableBounds = mScrolledBounds; viewableBounds.unite(mViewBounds); *bitBounds = contentBounds; bitBounds->intersect(overBounds); if (!bitBounds->intersects(viewableBounds)) *bitBounds = IntRect(0, 0, 0, 0); DBG_NAV_LOGD("contentBounds=(%d,%d,r=%d,b=%d) overBounds=(%d,%d,r=%d,b=%d)" " mScrolledBounds=(%d,%d,r=%d,b=%d) mViewBounds=(%d,%d,r=%d,b=%d)" " bitBounds=(%d,%d,r=%d,b=%d)", contentBounds.x(), contentBounds.y(), contentBounds.right(), contentBounds.bottom(), overBounds.x(), overBounds.y(), overBounds.right(), overBounds.bottom(), mScrolledBounds.x(), mScrolledBounds.y(), mScrolledBounds.right(), mScrolledBounds.bottom(), mViewBounds.x(), mViewBounds.y(), mViewBounds.right(), mViewBounds.bottom(), bitBounds->x(), bitBounds->y(), bitBounds->right(), bitBounds->bottom()); } int CachedRoot::checkForCenter(int x, int y) const { int width = mViewBounds.width(); SkPicture* picture = pictureAt(&x, &y); CenterCheck centerCheck(x + width - mViewBounds.x(), y - mViewBounds.y(), width); BoundsCanvas checker(¢erCheck); SkBitmap bitmap; bitmap.setConfig(SkBitmap::kARGB_8888_Config, width * 3, mViewBounds.height()); checker.setBitmapDevice(bitmap); checker.translate(SkIntToScalar(width - mViewBounds.x()), SkIntToScalar(-mViewBounds.y())); checker.drawPicture(*picture); return centerCheck.center(); } void CachedRoot::checkForJiggle(int* xDeltaPtr) const { int xDelta = *xDeltaPtr; JiggleCheck jiggleCheck(xDelta, mViewBounds.width()); BoundsCanvas checker(&jiggleCheck); SkBitmap bitmap; int absDelta = abs(xDelta); bitmap.setConfig(SkBitmap::kARGB_8888_Config, mViewBounds.width() + absDelta, mViewBounds.height()); checker.setBitmapDevice(bitmap); int x = -mViewBounds.x() - (xDelta < 0 ? xDelta : 0); int y = -mViewBounds.y(); SkPicture* picture = pictureAt(&x, &y); checker.translate(SkIntToScalar(x), SkIntToScalar(y)); checker.drawPicture(*picture); *xDeltaPtr = jiggleCheck.jiggle(); } bool CachedRoot::checkRings(SkPicture* picture, const CachedNode* node, const WebCore::IntRect& testBounds) const { if (!picture) return false; const WTF::Vector& rings = node->rings(); const WebCore::IntRect& nodeBounds = node->rawBounds(); IntRect bitBounds; calcBitBounds(nodeBounds, &bitBounds); RingCheck ringCheck(rings, bitBounds, testBounds, node->singleImage()); RingCanvas checker(&ringCheck); SkBitmap bitmap; bitmap.setConfig(SkBitmap::kARGB_8888_Config, bitBounds.width(), bitBounds.height()); checker.setBitmapDevice(bitmap); checker.translate(SkIntToScalar(-bitBounds.x()), SkIntToScalar(-bitBounds.y())); checker.drawPicture(*picture); bool result = ringCheck.textOutsideRings(); DBG_NAV_LOGD("bitBounds=(%d,%d,r=%d,b=%d) nodeBounds=(%d,%d,r=%d,b=%d)" " testBounds=(%d,%d,r=%d,b=%d) success=%s", bitBounds.x(), bitBounds.y(), bitBounds.right(), bitBounds.bottom(), nodeBounds.x(), nodeBounds.y(), nodeBounds.right(), nodeBounds.bottom(), testBounds.x(), testBounds.y(), testBounds.right(), testBounds.bottom(), result ? "true" : "false"); return result; } void CachedRoot::draw(FindCanvas& canvas) const { canvas.setLayerId(-1); // overlays change the ID as their pictures draw canvas.drawPicture(*mPicture); #if USE(ACCELERATED_COMPOSITING) if (!mRootLayer) return; canvas.drawLayers(mRootLayer); #endif } const CachedNode* CachedRoot::findAt(const WebCore::IntRect& rect, const CachedFrame** framePtr, int* x, int* y, bool checkForHidden) const { #if DEBUG_NAV_UI DBG_NAV_LOGD("rect=(%d,%d,w=%d,h=%d) xy=(%d,%d)", rect.x(), rect.y(), rect.width(), rect.height(), *x, *y); if (mRootLayer) CachedLayer::Debug::printRootLayerAndroid(mRootLayer); #endif int best = INT_MAX; bool inside = false; (const_cast(this))->resetClippedOut(); const CachedFrame* directHitFramePtr; const CachedNode* directHit = NULL; const CachedNode* node = findBestAt(rect, &best, &inside, &directHit, &directHitFramePtr, framePtr, x, y, checkForHidden); DBG_NAV_LOGD("node=%d (%p) xy=(%d,%d)", node == NULL ? 0 : node->index(), node == NULL ? NULL : node->nodePointer(), *x, *y); if (node == NULL) { node = findBestHitAt(rect, framePtr, x, y); DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(), node == NULL ? NULL : node->nodePointer()); } if (node == NULL) { *framePtr = findBestFrameAt(rect.x() + (rect.width() >> 1), rect.y() + (rect.height() >> 1)); } return node; } WebCore::IntPoint CachedRoot::cursorLocation() const { const WebCore::IntRect& bounds = mHistory->mNavBounds; return WebCore::IntPoint(bounds.x() + (bounds.width() >> 1), bounds.y() + (bounds.height() >> 1)); } WebCore::IntPoint CachedRoot::focusLocation() const { return WebCore::IntPoint(mFocusBounds.x() + (mFocusBounds.width() >> 1), mFocusBounds.y() + (mFocusBounds.height() >> 1)); } // These reset the values because we only want to get the selection the first time. // After that, the selection is no longer accurate. int CachedRoot::getAndResetSelectionEnd() { int end = mSelectionEnd; mSelectionEnd = -1; return end; } int CachedRoot::getAndResetSelectionStart() { int start = mSelectionStart; mSelectionStart = -1; return start; } int CachedRoot::getBlockLeftEdge(int x, int y, float scale) const { DBG_NAV_LOGD("x=%d y=%d scale=%g mViewBounds=(%d,%d,%d,%d)", x, y, scale, mViewBounds.x(), mViewBounds.y(), mViewBounds.width(), mViewBounds.height()); // if (x, y) is in a textArea or textField, return that const int slop = 1; WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop, slop * 2, slop * 2); const CachedFrame* frame; int fx, fy; const CachedNode* node = findAt(rect, &frame, &fx, &fy, true); if (node && node->wantsKeyEvents()) { DBG_NAV_LOGD("x=%d (%s)", node->bounds(frame).x(), node->isTextInput() ? "text" : "plugin"); return node->bounds(frame).x(); } SkPicture* picture = node ? frame->picture(node, &x, &y) : pictureAt(&x, &y); if (!picture) return x; int halfW = (int) (mViewBounds.width() * scale * 0.5f); int fullW = halfW << 1; int halfH = (int) (mViewBounds.height() * scale * 0.5f); int fullH = halfH << 1; LeftCheck leftCheck(fullW, halfH); BoundsCanvas checker(&leftCheck); SkBitmap bitmap; bitmap.setConfig(SkBitmap::kARGB_8888_Config, fullW, fullH); checker.setBitmapDevice(bitmap); checker.translate(SkIntToScalar(fullW - x), SkIntToScalar(halfH - y)); checker.drawPicture(*picture); int result = x + leftCheck.left() - fullW; DBG_NAV_LOGD("halfW=%d halfH=%d mMostLeft=%d x=%d", halfW, halfH, leftCheck.mMostLeft, result); return result; } void CachedRoot::getSimulatedMousePosition(WebCore::IntPoint* point) const { #ifndef NDEBUG ASSERT(CachedFrame::mDebug.mInUse); #endif const WebCore::IntRect& mouseBounds = mHistory->mMouseBounds; int x = mouseBounds.x(); int y = mouseBounds.y(); int width = mouseBounds.width(); int height = mouseBounds.height(); point->setX(x + (width >> 1)); // default to box center point->setY(y + (height >> 1)); #if DEBUG_NAV_UI const WebCore::IntRect& navBounds = mHistory->mNavBounds; DBG_NAV_LOGD("mHistory->mNavBounds={%d,%d,%d,%d} " "mHistory->mMouseBounds={%d,%d,%d,%d} point={%d,%d}", navBounds.x(), navBounds.y(), navBounds.width(), navBounds.height(), mouseBounds.x(), mouseBounds.y(), mouseBounds.width(), mouseBounds.height(), point->x(), point->y()); #endif } void CachedRoot::init(WebCore::Frame* frame, CachedHistory* history) { CachedFrame::init(this, -1, frame); reset(); mHistory = history; mPicture = NULL; } bool CachedRoot::innerDown(const CachedNode* test, BestData* bestData) const { ASSERT(minWorkingVertical() >= mViewBounds.x()); ASSERT(maxWorkingVertical() <= mViewBounds.right()); setupScrolledBounds(); // (line up) mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); int testTop = mScrolledBounds.y(); int viewBottom = mViewBounds.bottom(); const WebCore::IntRect& navBounds = mHistory->mNavBounds; if (navBounds.isEmpty() == false && navBounds.bottom() > viewBottom && viewBottom < mContents.height()) return false; if (navBounds.isEmpty() == false) { int navTop = navBounds.y(); int scrollBottom; if (testTop < navTop && navTop < (scrollBottom = mScrolledBounds.bottom())) { mScrolledBounds.setHeight(scrollBottom - navTop); mScrolledBounds.setY(navTop); } } setCursorCache(0, mMaxYScroll); frameDown(test, NULL, bestData); return true; } bool CachedRoot::innerLeft(const CachedNode* test, BestData* bestData) const { ASSERT(minWorkingHorizontal() >= mViewBounds.y()); ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom()); setupScrolledBounds(); mScrolledBounds.setX(mScrolledBounds.x() - mMaxXScroll); mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); int testRight = mScrolledBounds.right(); int viewLeft = mViewBounds.x(); const WebCore::IntRect& navBounds = mHistory->mNavBounds; if (navBounds.isEmpty() == false && navBounds.x() < viewLeft && viewLeft > mContents.x()) return false; if (navBounds.isEmpty() == false) { int navRight = navBounds.right(); int scrollLeft; if (testRight > navRight && navRight > (scrollLeft = mScrolledBounds.x())) mScrolledBounds.setWidth(navRight - scrollLeft); } setCursorCache(-mMaxXScroll, 0); frameLeft(test, NULL, bestData); return true; } void CachedRoot::innerMove(const CachedNode* node, BestData* bestData, Direction direction, WebCore::IntPoint* scroll, bool firstCall) { bestData->reset(); bool outOfCursor = mCursorIndex == CURSOR_CLEARED; DBG_NAV_LOGD("mHistory->didFirstLayout()=%s && mCursorIndex=%d", mHistory->didFirstLayout() ? "true" : "false", mCursorIndex); if (mHistory->didFirstLayout() && mCursorIndex < CURSOR_SET) { mHistory->reset(); outOfCursor = true; } const CachedFrame* cursorFrame; const CachedNode* cursor = currentCursor(&cursorFrame); mHistory->setWorking(direction, cursorFrame, cursor, mViewBounds); bool findClosest = false; if (mScrollOnly == false) { switch (direction) { case LEFT: if (outOfCursor) mHistory->mNavBounds = WebCore::IntRect(mViewBounds.right(), mViewBounds.y(), 1, mViewBounds.height()); findClosest = innerLeft(node, bestData); break; case RIGHT: if (outOfCursor) mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x() - 1, mViewBounds.y(), 1, mViewBounds.height()); findClosest = innerRight(node, bestData); break; case UP: if (outOfCursor) mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), mViewBounds.bottom(), mViewBounds.width(), 1); findClosest = innerUp(node, bestData); break; case DOWN: if (outOfCursor) mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), mViewBounds.y() - 1, mViewBounds.width(), 1); findClosest = innerDown(node, bestData); break; case UNINITIALIZED: default: ASSERT(0); } } if (firstCall) mHistory->mPriorBounds = mHistory->mNavBounds; // bounds always advances, even if new node is ultimately NULL bestData->setMouseBounds(bestData->bounds()); if (adjustForScroll(bestData, direction, scroll, findClosest)) return; if (bestData->mNode != NULL) { mHistory->addToVisited(bestData->mNode, direction); mHistory->mNavBounds = bestData->bounds(); mHistory->mMouseBounds = bestData->mouseBounds(); } else if (scroll->x() != 0 || scroll->y() != 0) { WebCore::IntRect newBounds = mHistory->mNavBounds; int offsetX = scroll->x(); int offsetY = scroll->y(); newBounds.move(offsetX, offsetY); if (mViewBounds.x() > newBounds.x()) offsetX = mViewBounds.x() - mHistory->mNavBounds.x(); else if (mViewBounds.right() < newBounds.right()) offsetX = mViewBounds.right() - mHistory->mNavBounds.right(); if (mViewBounds.y() > newBounds.y()) offsetY = mViewBounds.y() - mHistory->mNavBounds.y(); else if (mViewBounds.bottom() < newBounds.bottom()) offsetY = mViewBounds.bottom() - mHistory->mNavBounds.bottom(); mHistory->mNavBounds.move(offsetX, offsetY); } mHistory->setDidFirstLayout(false); } bool CachedRoot::innerRight(const CachedNode* test, BestData* bestData) const { ASSERT(minWorkingHorizontal() >= mViewBounds.y()); ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom()); setupScrolledBounds(); // (align) mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); int testLeft = mScrolledBounds.x(); int viewRight = mViewBounds.right(); const WebCore::IntRect& navBounds = mHistory->mNavBounds; if (navBounds.isEmpty() == false && navBounds.right() > viewRight && viewRight < mContents.width()) return false; if (navBounds.isEmpty() == false) { int navLeft = navBounds.x(); int scrollRight; if (testLeft < navLeft && navLeft < (scrollRight = mScrolledBounds.right())) { mScrolledBounds.setWidth(scrollRight - navLeft); mScrolledBounds.setX(navLeft); } } setCursorCache(mMaxXScroll, 0); frameRight(test, NULL, bestData); return true; } bool CachedRoot::innerUp(const CachedNode* test, BestData* bestData) const { ASSERT(minWorkingVertical() >= mViewBounds.x()); ASSERT(maxWorkingVertical() <= mViewBounds.right()); setupScrolledBounds(); mScrolledBounds.setY(mScrolledBounds.y() - mMaxYScroll); mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); int testBottom = mScrolledBounds.bottom(); int viewTop = mViewBounds.y(); const WebCore::IntRect& navBounds = mHistory->mNavBounds; if (navBounds.isEmpty() == false && navBounds.y() < viewTop && viewTop > mContents.y()) return false; if (navBounds.isEmpty() == false) { int navBottom = navBounds.bottom(); int scrollTop; if (testBottom > navBottom && navBottom > (scrollTop = mScrolledBounds.y())) mScrolledBounds.setHeight(navBottom - scrollTop); } setCursorCache(0, -mMaxYScroll); frameUp(test, NULL, bestData); return true; } WTF::String CachedRoot::imageURI(int x, int y) const { DBG_NAV_LOGD("x/y=(%d,%d)", x, y); ImageCheck imageCheck; ImageCanvas checker(&imageCheck); SkBitmap bitmap; bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1); checker.setBitmapDevice(bitmap); SkPicture* picture = pictureAt(&x, &y); checker.translate(SkIntToScalar(-x), SkIntToScalar(-y)); checker.drawPicture(*picture); DBG_NAV_LOGD("uri=%s", checker.getURI()); return WTF::String(checker.getURI()); } bool CachedRoot::maskIfHidden(BestData* best) const { const CachedNode* bestNode = best->mNode; if (bestNode->isUnclipped()) return false; const CachedFrame* frame = best->mFrame; SkPicture* picture = frame->picture(bestNode); if (picture == NULL) { DBG_NAV_LOG("missing picture"); return false; } Vector rings; bestNode->cursorRings(frame, &rings); const WebCore::IntRect& bounds = bestNode->bounds(frame); IntRect bitBounds; calcBitBounds(bounds, &bitBounds); RingCheck ringCheck(rings, bitBounds, bounds, bestNode->singleImage()); RingCanvas checker(&ringCheck); SkBitmap bitmap; bitmap.setConfig(SkBitmap::kARGB_8888_Config, bitBounds.width(), bitBounds.height()); checker.setBitmapDevice(bitmap); checker.translate(SkIntToScalar(-bitBounds.x()), SkIntToScalar(-bitBounds.y())); checker.drawPicture(*picture); SkRegion clipRgn; bool clipped = ringCheck.hiddenRings(&clipRgn); CachedNode* node = const_cast(best->mNode); DBG_NAV_LOGD("clipped=%s clipRgn.isEmpty=%s", clipped ? "true" : "false", clipRgn.isEmpty() ? "true" : "false"); if (clipped && clipRgn.isEmpty()) { node->setDisabled(true); IntRect clippedBounds = bounds; clippedBounds.intersect(bitBounds); node->setClippedOut(clippedBounds != bounds); return true; } // was it partially occluded by later drawing? // if partially occluded, modify the bounds so that the mouse click has a better x,y if (clipped) { DBG_NAV_LOGD("clipped clipRgn={%d,%d,r=%d,b=%d}", clipRgn.getBounds().fLeft, clipRgn.getBounds().fTop, clipRgn.getBounds().fRight, clipRgn.getBounds().fBottom); best->setMouseBounds(clipRgn.getBounds()); if (!node->clip(best->mouseBounds())) { node->setDisabled(true); node->setClippedOut(true); return true; } } else node->fixUpCursorRects(frame); return false; } const CachedNode* CachedRoot::moveCursor(Direction direction, const CachedFrame** framePtr, WebCore::IntPoint* scroll) { #ifndef NDEBUG ASSERT(CachedFrame::mDebug.mInUse); #endif CachedRoot* frame = this; const CachedNode* node = frame->document(); if (node == NULL) return NULL; if (mViewBounds.isEmpty()) return NULL; resetClippedOut(); setData(); BestData bestData; innerMove(node, &bestData, direction, scroll, true); // if node is partially or fully concealed by layer, scroll it into view if (mRootLayer && bestData.mNode && !bestData.mNode->isInLayer()) { #if USE(ACCELERATED_COMPOSITING) #if DUMP_NAV_CACHE CachedLayer::Debug::printRootLayerAndroid(mRootLayer); #endif SkIRect original = bestData.mNode->cursorRingBounds(bestData.mFrame); DBG_NAV_LOGD("original=(%d,%d,w=%d,h=%d) scroll=(%d,%d)", original.fLeft, original.fTop, original.width(), original.height(), scroll->x(), scroll->y()); original.offset(-scroll->x(), -scroll->y()); SkRegion rings(original); SkTDArray region; mRootLayer->clipArea(®ion); SkRegion layers; for (int index = 0; index < region.count(); index++) { SkIRect enclosing; region[index].round(&enclosing); rings.op(enclosing, SkRegion::kDifference_Op); layers.op(enclosing, SkRegion::kUnion_Op); } SkIRect layerBounds(layers.getBounds()); SkIRect ringBounds(rings.getBounds()); int scrollX = scroll->x(); int scrollY = scroll->y(); if (rings.getBounds() != original) { int topOverlap = layerBounds.fBottom - original.fTop; int bottomOverlap = original.fBottom - layerBounds.fTop; int leftOverlap = layerBounds.fRight - original.fLeft; int rightOverlap = original.fRight - layerBounds.fLeft; if (direction & UP_DOWN) { if (layerBounds.fLeft < original.fLeft && leftOverlap < 0) scroll->setX(leftOverlap); if (original.fRight < layerBounds.fRight && rightOverlap > 0 && -leftOverlap > rightOverlap) scroll->setX(rightOverlap); bool topSet = scrollY > topOverlap && (direction == UP || !scrollY); if (topSet) scroll->setY(topOverlap); if (scrollY < bottomOverlap && (direction == DOWN || (!scrollY && (!topSet || -topOverlap > bottomOverlap)))) scroll->setY(bottomOverlap); } else { if (layerBounds.fTop < original.fTop && topOverlap < 0) scroll->setY(topOverlap); if (original.fBottom < layerBounds.fBottom && bottomOverlap > 0 && -topOverlap > bottomOverlap) scroll->setY(bottomOverlap); bool leftSet = scrollX > leftOverlap && (direction == LEFT || !scrollX); if (leftSet) scroll->setX(leftOverlap); if (scrollX < rightOverlap && (direction == RIGHT || (!scrollX && (!leftSet || -leftOverlap > rightOverlap)))) scroll->setX(rightOverlap); } DBG_NAV_LOGD("rings=(%d,%d,w=%d,h=%d) layers=(%d,%d,w=%d,h=%d)" " scroll=(%d,%d)", ringBounds.fLeft, ringBounds.fTop, ringBounds.width(), ringBounds.height(), layerBounds.fLeft, layerBounds.fTop, layerBounds.width(), layerBounds.height(), scroll->x(), scroll->y()); } #endif } *framePtr = bestData.mFrame; return const_cast(bestData.mNode); } const CachedNode* CachedRoot::nextTextField(const CachedNode* start, const CachedFrame** framePtr) const { bool startFound = false; return CachedFrame::nextTextField(start, framePtr, &startFound); } SkPicture* CachedRoot::pictureAt(int* xPtr, int* yPtr, int* id) const { #if USE(ACCELERATED_COMPOSITING) if (mRootLayer) { const LayerAndroid* layer = mRootLayer->find(xPtr, yPtr, mPicture); if (layer) { SkPicture* picture = layer->picture(); DBG_NAV_LOGD("layer %d picture=%p (%d,%d)", layer->uniqueId(), picture, picture ? picture->width() : 0, picture ? picture->height() : 0); if (picture) { if (id) *id = layer->uniqueId(); return picture; } } } #endif DBG_NAV_LOGD("root mPicture=%p (%d,%d)", mPicture, mPicture ? mPicture->width() : 0, mPicture ? mPicture->height() : 0); if (id) *id = -1; return mPicture; } void CachedRoot::reset() { #ifndef NDEBUG ASSERT(CachedFrame::mDebug.mInUse); #endif mContents = mViewBounds = WebCore::IntRect(0, 0, 0, 0); mMaxXScroll = mMaxYScroll = 0; mRootLayer = 0; mSelectionStart = mSelectionEnd = -1; mScrollOnly = false; } bool CachedRoot::scrollDelta(WebCore::IntRect& newOutset, Direction direction, int* delta) { switch (direction) { case LEFT: *delta = -mMaxXScroll; return newOutset.x() >= mViewBounds.x(); case RIGHT: *delta = mMaxXScroll; return newOutset.right() <= mViewBounds.right(); case UP: *delta = -mMaxYScroll; return newOutset.y() >= mViewBounds.y(); case DOWN: *delta = mMaxYScroll; return newOutset.bottom() <= mViewBounds.bottom(); default: *delta = 0; ASSERT(0); } return false; } void CachedRoot::setCachedFocus(CachedFrame* frame, CachedNode* node) { mFocusBounds = WebCore::IntRect(0, 0, 0, 0); if (node == NULL) return; node->setIsFocus(true); mFocusBounds = node->bounds(frame); frame->setFocusIndex(node - frame->document()); CachedFrame* parent; while ((parent = frame->parent()) != NULL) { parent->setFocusIndex(frame->indexInParent()); frame = parent; } #if DEBUG_NAV_UI const CachedFrame* focusFrame; const CachedNode* focus = currentFocus(&focusFrame); WebCore::IntRect bounds = WebCore::IntRect(0, 0, 0, 0); if (focus) bounds = focus->bounds(focusFrame); DBG_NAV_LOGD("new focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}", focus ? focus->index() : 0, focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(), bounds.width(), bounds.height()); #endif } void CachedRoot::setCursor(CachedFrame* frame, CachedNode* node) { #if DEBUG_NAV_UI const CachedFrame* cursorFrame; const CachedNode* cursor = currentCursor(&cursorFrame); WebCore::IntRect bounds; if (cursor) bounds = cursor->bounds(cursorFrame); DBG_NAV_LOGD("old cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}", cursor ? cursor->index() : 0, cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(), bounds.width(), bounds.height()); #endif clearCursor(); if (node == NULL) return; node->setIsCursor(true); node->show(); frame->setCursorIndex(node - frame->document()); CachedFrame* parent; while ((parent = frame->parent()) != NULL) { parent->setCursorIndex(frame->indexInParent()); frame = parent; } #if DEBUG_NAV_UI cursor = currentCursor(&cursorFrame); bounds = WebCore::IntRect(0, 0, 0, 0); if (cursor) bounds = cursor->bounds(cursorFrame); DBG_NAV_LOGD("new cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}", cursor ? cursor->index() : 0, cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(), bounds.width(), bounds.height()); #endif } void CachedRoot::setCursorCache(int scrollX, int scrollY) const { mCursor = currentCursor(); if (mCursor) mCursorBounds = mCursor->bounds(this); if (!mRootLayer) return; SkRegion baseScrolled(mScrolledBounds); mBaseUncovered = SkRegion(mScrolledBounds); #if USE(ACCELERATED_COMPOSITING) #if DUMP_NAV_CACHE CachedLayer::Debug::printRootLayerAndroid(mRootLayer); #endif SkTDArray region; mRootLayer->clipArea(®ion); WebCore::IntSize offset( copysign(min(max(0, mContents.width() - mScrolledBounds.width()), abs(scrollX)), scrollX), copysign(min(max(0, mContents.height() - mScrolledBounds.height()), abs(scrollY)), scrollY)); bool hasOffset = offset.width() || offset.height(); // restrict scrollBounds to that which is not under layer for (int index = 0; index < region.count(); index++) { SkIRect less; region[index].round(&less); DBG_NAV_LOGD("less=(%d,%d,w=%d,h=%d)", less.fLeft, less.fTop, less.width(), less.height()); mBaseUncovered.op(less, SkRegion::kDifference_Op); if (!hasOffset) continue; less.offset(offset.width(), offset.height()); baseScrolled.op(less, SkRegion::kDifference_Op); } if (hasOffset) mBaseUncovered.op(baseScrolled, SkRegion::kUnion_Op); #endif } #if DUMP_NAV_CACHE #define DEBUG_PRINT_BOOL(field) \ DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") #define DEBUG_PRINT_RECT(field) \ { const WebCore::IntRect& r = b->field; \ DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ r.x(), r.y(), r.width(), r.height()); } CachedRoot* CachedRoot::Debug::base() const { CachedRoot* nav = (CachedRoot*) ((char*) this - OFFSETOF(CachedRoot, mDebug)); return nav; } void CachedRoot::Debug::print() const { #ifdef DUMP_NAV_CACHE_USING_PRINTF gWriteLogMutex.lock(); ASSERT(gNavCacheLogFile == NULL); gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a"); #endif CachedRoot* b = base(); b->CachedFrame::mDebug.print(); b->mHistory->mDebug.print(b); DUMP_NAV_LOGD("// int mMaxXScroll=%d, mMaxYScroll=%d;\n", b->mMaxXScroll, b->mMaxYScroll); if (b->mRootLayer) CachedLayer::Debug::printRootLayerAndroid(b->mRootLayer); #ifdef DUMP_NAV_CACHE_USING_PRINTF if (gNavCacheLogFile) fclose(gNavCacheLogFile); gNavCacheLogFile = NULL; gWriteLogMutex.unlock(); #endif } #endif }