Home | History | Annotate | Download | only in nav
      1 /*
      2  * Copyright 2007, The Android Open Source Project
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *  * Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  *  * Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "CachedPrefix.h"
     27 #include "android_graphics.h"
     28 #include "CachedHistory.h"
     29 #include "CachedInput.h"
     30 #include "CachedNode.h"
     31 #include "FindCanvas.h"
     32 #include "FloatRect.h"
     33 #include "LayerAndroid.h"
     34 #include "SkBitmap.h"
     35 #include "SkBounder.h"
     36 #include "SkPixelRef.h"
     37 #include "SkRegion.h"
     38 
     39 #include "CachedRoot.h"
     40 
     41 using std::min;
     42 using std::max;
     43 
     44 #ifdef DUMP_NAV_CACHE_USING_PRINTF
     45     extern android::Mutex gWriteLogMutex;
     46 #endif
     47 
     48 namespace android {
     49 
     50 class CommonCheck : public SkBounder {
     51 public:
     52     enum Type {
     53         kNo_Type,
     54         kDrawBitmap_Type,
     55         kDrawGlyph_Type,
     56         kDrawPaint_Type,
     57         kDrawPath_Type,
     58         kDrawPicture_Type,
     59         kDrawPoints_Type,
     60         kDrawPosText_Type,
     61         kDrawPosTextH_Type,
     62         kDrawRect_Type,
     63         kDrawSprite_Type,
     64         kDrawText_Type,
     65         kDrawTextOnPath_Type
     66     };
     67 
     68     static bool isTextType(Type t) {
     69         return t == kDrawPosTextH_Type || t == kDrawText_Type;
     70     }
     71 
     72     CommonCheck() : mType(kNo_Type), mAllOpaque(true), mIsOpaque(true) {
     73         setEmpty();
     74     }
     75 
     76     bool doRect(Type type) {
     77         mType = type;
     78         return doIRect(mUnion);
     79     }
     80 
     81     bool joinGlyphs(const SkIRect& rect) {
     82         bool isGlyph = mType == kDrawGlyph_Type;
     83         if (isGlyph)
     84             mUnion.join(rect);
     85         return isGlyph;
     86     }
     87 
     88     void setAllOpaque(bool opaque) { mAllOpaque = opaque; }
     89     void setEmpty() { mUnion.setEmpty(); }
     90     void setIsOpaque(bool opaque) { mIsOpaque = opaque; }
     91     void setType(Type type) { mType = type; }
     92 
     93     Type mType;
     94     SkIRect mUnion;
     95     bool mAllOpaque;
     96     bool mIsOpaque;
     97 };
     98 
     99 #if DEBUG_NAV_UI
    100     static const char* TypeNames[] = {
    101         "kNo_Type",
    102         "kDrawBitmap_Type",
    103         "kDrawGlyph_Type",
    104         "kDrawPaint_Type",
    105         "kDrawPath_Type",
    106         "kDrawPicture_Type",
    107         "kDrawPoints_Type",
    108         "kDrawPosText_Type",
    109         "kDrawPosTextH_Type",
    110         "kDrawRect_Type",
    111         "kDrawSprite_Type",
    112         "kDrawText_Type",
    113         "kDrawTextOnPath_Type"
    114     };
    115 #endif
    116 
    117 #define kMargin 16
    118 #define kSlop 2
    119 
    120 class BoundsCheck : public CommonCheck {
    121 public:
    122     BoundsCheck() {
    123         mAllDrawnIn.setEmpty();
    124         mLastAll.setEmpty();
    125         mLastOver.setEmpty();
    126     }
    127 
    128     static int Area(SkIRect test) {
    129         return test.width() * test.height();
    130     }
    131 
    132    void checkLast() {
    133         if (mAllDrawnIn.isEmpty())
    134             return;
    135         if (mLastAll.isEmpty() || Area(mLastAll) < Area(mAllDrawnIn)) {
    136             mLastAll = mAllDrawnIn;
    137             mDrawnOver.setEmpty();
    138         }
    139         mAllDrawnIn.setEmpty();
    140     }
    141 
    142     bool hidden() {
    143         return (mLastAll.isEmpty() && mLastOver.isEmpty()) ||
    144             mDrawnOver.contains(mBounds);
    145     }
    146 
    147     virtual bool onIRect(const SkIRect& rect) {
    148         if (joinGlyphs(rect))
    149             return false;
    150         bool interestingType = mType == kDrawBitmap_Type ||
    151             mType == kDrawRect_Type || isTextType(mType);
    152         if (SkIRect::Intersects(mBounds, rect) == false) {
    153             DBG_NAV_LOGD("BoundsCheck (no intersect) rect={%d,%d,%d,%d}"
    154                 " mType=%s", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
    155                 TypeNames[mType]);
    156             if (interestingType)
    157                 checkLast();
    158             return false;
    159         }
    160         if (interestingType == false)
    161             return false;
    162         if (!mDrawnOver.contains(rect) && (mBoundsSlop.contains(rect) ||
    163                 (mBounds.fLeft == rect.fLeft && mBounds.fRight == rect.fRight &&
    164                 mBounds.fTop >= rect.fTop && mBounds.fBottom <= rect.fBottom) ||
    165                 (mBounds.fTop == rect.fTop && mBounds.fBottom == rect.fBottom &&
    166                 mBounds.fLeft >= rect.fLeft && mBounds.fRight <= rect.fRight))) {
    167             mDrawnOver.setEmpty();
    168             mAllDrawnIn.join(rect);
    169             DBG_NAV_LOGD("BoundsCheck (contains) rect={%d,%d,%d,%d}"
    170                 " mAllDrawnIn={%d,%d,%d,%d} mType=%s",
    171                 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
    172                 mAllDrawnIn.fLeft, mAllDrawnIn.fTop, mAllDrawnIn.fRight,
    173                 mAllDrawnIn.fBottom, TypeNames[mType]);
    174        } else {
    175             checkLast();
    176             if (!isTextType(mType)) {
    177                 if (
    178 #if 0
    179 // should the opaqueness of the bitmap disallow its ability to draw over?
    180 // not sure that this test is needed
    181                 (mType != kDrawBitmap_Type ||
    182                         (mIsOpaque && mAllOpaque)) &&
    183 #endif
    184                         mLastAll.isEmpty() == false)
    185                     mDrawnOver.op(rect, SkRegion::kUnion_Op);
    186             } else {
    187 // FIXME
    188 // sometimes the text is not drawn entirely inside the cursor area, even though
    189 // it is the correct text. Until I figure out why, I allow text drawn at the
    190 // end that is not covered up by something else to represent the link
    191 // example that triggers this that should be figured out:
    192 // http://cdn.labpixies.com/campaigns/blackjack/blackjack.html?lang=en&country=US&libs=assets/feature/core
    193 // ( http://tinyurl.com/ywsyzb )
    194                 mLastOver = rect;
    195             }
    196 #if DEBUG_NAV_UI
    197         const SkIRect& drawnOver = mDrawnOver.getBounds();
    198         DBG_NAV_LOGD("(overlaps) rect={%d,%d,%d,%d}"
    199             " mDrawnOver={%d,%d,%d,%d} mType=%s mIsOpaque=%s mAllOpaque=%s",
    200             rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
    201             drawnOver.fLeft, drawnOver.fTop, drawnOver.fRight, drawnOver.fBottom,
    202             TypeNames[mType], mIsOpaque ? "true" : "false",
    203             mAllOpaque ? "true" : "false");
    204 #endif
    205         }
    206         return false;
    207     }
    208 
    209     SkIRect mBounds;
    210     SkIRect mBoundsSlop;
    211     SkRegion mDrawnOver;
    212     SkIRect mLastOver;
    213     SkIRect mAllDrawnIn;
    214     SkIRect mLastAll;
    215 };
    216 
    217 class BoundsCanvas : public SkCanvas {
    218 public:
    219 
    220     BoundsCanvas(CommonCheck* bounder) : mBounder(*bounder) {
    221         mTransparentLayer = 0;
    222         setBounder(bounder);
    223     }
    224 
    225     virtual ~BoundsCanvas() {
    226         setBounder(NULL);
    227     }
    228 
    229     virtual void drawPaint(const SkPaint& paint) {
    230         mBounder.setType(CommonCheck::kDrawPaint_Type);
    231         SkCanvas::drawPaint(paint);
    232     }
    233 
    234     virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
    235                             const SkPaint& paint) {
    236         mBounder.setType(CommonCheck::kDrawPoints_Type);
    237         SkCanvas::drawPoints(mode, count, pts, paint);
    238     }
    239 
    240     virtual void drawRect(const SkRect& rect, const SkPaint& paint) {
    241         mBounder.setType(CommonCheck::kDrawRect_Type);
    242         SkCanvas::drawRect(rect, paint);
    243     }
    244 
    245     virtual void drawPath(const SkPath& path, const SkPaint& paint) {
    246         mBounder.setType(CommonCheck::kDrawPath_Type);
    247         SkCanvas::drawPath(path, paint);
    248     }
    249 
    250     virtual void commonDrawBitmap(const SkBitmap& bitmap,
    251                               const SkMatrix& matrix, const SkPaint& paint) {
    252         mBounder.setType(CommonCheck::kDrawBitmap_Type);
    253         mBounder.setIsOpaque(bitmap.isOpaque());
    254         SkCanvas::commonDrawBitmap(bitmap, matrix, paint);
    255     }
    256 
    257     virtual void drawSprite(const SkBitmap& bitmap, int left, int top,
    258                             const SkPaint* paint = NULL) {
    259         mBounder.setType(CommonCheck::kDrawSprite_Type);
    260         mBounder.setIsOpaque(bitmap.isOpaque());
    261         SkCanvas::drawSprite(bitmap, left, top, paint);
    262     }
    263 
    264     virtual void drawText(const void* text, size_t byteLength, SkScalar x,
    265                           SkScalar y, const SkPaint& paint) {
    266         mBounder.setEmpty();
    267         mBounder.setType(CommonCheck::kDrawGlyph_Type);
    268         SkCanvas::drawText(text, byteLength, x, y, paint);
    269         mBounder.doRect(CommonCheck::kDrawText_Type);
    270     }
    271 
    272     virtual void drawPosText(const void* text, size_t byteLength,
    273                              const SkPoint pos[], const SkPaint& paint) {
    274         mBounder.setEmpty();
    275         mBounder.setType(CommonCheck::kDrawGlyph_Type);
    276         SkCanvas::drawPosText(text, byteLength, pos, paint);
    277         mBounder.doRect(CommonCheck::kDrawPosText_Type);
    278     }
    279 
    280     virtual void drawPosTextH(const void* text, size_t byteLength,
    281                               const SkScalar xpos[], SkScalar constY,
    282                               const SkPaint& paint) {
    283         mBounder.setEmpty();
    284         mBounder.setType(CommonCheck::kDrawGlyph_Type);
    285         SkCanvas::drawPosTextH(text, byteLength, xpos, constY, paint);
    286         if (mBounder.mUnion.isEmpty())
    287             return;
    288         SkPaint::FontMetrics metrics;
    289         paint.getFontMetrics(&metrics);
    290         SkPoint upDown[2] = { {xpos[0], constY + metrics.fAscent},
    291             {xpos[0], constY + metrics.fDescent} };
    292         const SkMatrix& matrix = getTotalMatrix();
    293         matrix.mapPoints(upDown, 2);
    294         if (upDown[0].fX == upDown[1].fX) {
    295             mBounder.mUnion.fTop = SkScalarFloor(upDown[0].fY);
    296             mBounder.mUnion.fBottom = SkScalarFloor(upDown[1].fY);
    297         }
    298         mBounder.doRect(CommonCheck::kDrawPosTextH_Type);
    299     }
    300 
    301     virtual void drawTextOnPath(const void* text, size_t byteLength,
    302                                 const SkPath& path, const SkMatrix* matrix,
    303                                 const SkPaint& paint) {
    304         mBounder.setEmpty();
    305         mBounder.setType(CommonCheck::kDrawGlyph_Type);
    306         SkCanvas::drawTextOnPath(text, byteLength, path, matrix, paint);
    307         mBounder.doRect(CommonCheck::kDrawTextOnPath_Type);
    308     }
    309 
    310     virtual void drawPicture(SkPicture& picture) {
    311         mBounder.setType(CommonCheck::kDrawPicture_Type);
    312         SkCanvas::drawPicture(picture);
    313     }
    314 
    315     virtual int saveLayer(const SkRect* bounds, const SkPaint* paint,
    316                           SaveFlags flags) {
    317         int depth = SkCanvas::saveLayer(bounds, paint, flags);
    318         if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) {
    319             mTransparentLayer = depth;
    320             mBounder.setAllOpaque(false);
    321         }
    322         return depth;
    323     }
    324 
    325     virtual void restore() {
    326         int depth = getSaveCount();
    327         if (depth == mTransparentLayer) {
    328             mTransparentLayer = 0;
    329             mBounder.setAllOpaque(true);
    330         }
    331         SkCanvas::restore();
    332     }
    333 
    334     int mTransparentLayer;
    335     CommonCheck& mBounder;
    336 };
    337 
    338 /*
    339 LeftCheck examines the text in a picture, within a viewable rectangle,
    340 and returns via left() the position of the left edge of the paragraph.
    341 It first looks at the left edge of the test point, then looks above and below
    342 it for more lines of text to determine the div's left edge.
    343 */
    344 class LeftCheck : public CommonCheck {
    345 public:
    346     LeftCheck(int x, int y) : mX(x), mY(y), mHitLeft(INT_MAX),
    347             mMostLeft(INT_MAX) {
    348         mHit.set(x - (HIT_SLOP << 1), y - HIT_SLOP, x, y + HIT_SLOP);
    349         mPartial.setEmpty();
    350         mBounds.setEmpty();
    351         mPartialType = kNo_Type;
    352     }
    353 
    354     int left() {
    355         if (isTextType(mType))
    356             doRect(); // process the final line of text
    357         return mMostLeft != INT_MAX ? mMostLeft : mX >> 1;
    358     }
    359 
    360     // FIXME: this is identical to CenterCheck::onIRect()
    361     // refactor so that LeftCheck and CenterCheck inherit common functions
    362     virtual bool onIRect(const SkIRect& rect) {
    363         bool opaqueBitmap = mType == kDrawBitmap_Type && mIsOpaque;
    364         if (opaqueBitmap && rect.contains(mX, mY)) {
    365             mMostLeft = rect.fLeft;
    366             return false;
    367         }
    368         if (joinGlyphs(rect)) // assembles glyphs into a text string
    369             return false;
    370         if (!isTextType(mType) && !opaqueBitmap)
    371             return false;
    372         /* Text on one line may be broken into several parts. Reassemble
    373            the text into a rectangle before considering it. */
    374         if (rect.fTop < mPartial.fBottom
    375                 && rect.fBottom > mPartial.fTop
    376                 && mPartial.fRight + SLOP >= rect.fLeft
    377                 && (mPartialType != kDrawBitmap_Type
    378                 || mPartial.height() <= rect.height() + HIT_SLOP)) {
    379             DBG_NAV_LOGD("LeftCheck join mPartial=(%d, %d, %d, %d)"
    380                 " rect=(%d, %d, %d, %d)",
    381                 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom,
    382                 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
    383             mPartial.join(rect);
    384             return false;
    385         }
    386         if (mPartial.isEmpty() == false) {
    387             doRect(); // process the previous line of text
    388 #if DEBUG_NAV_UI
    389             if (mHitLeft == INT_MAX)
    390                 DBG_NAV_LOGD("LeftCheck disabled rect=(%d, %d, %d, %d)",
    391                     rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
    392 #endif
    393         }
    394         mPartial = rect;
    395         mPartialType = mType;
    396         return false;
    397     }
    398 
    399     void doRect()
    400     {
    401         /* Record the outer bounds of the lines of text that intersect the
    402            touch coordinates, given some slop */
    403         if (SkIRect::Intersects(mPartial, mHit)) {
    404             if (mHitLeft > mPartial.fLeft)
    405                 mHitLeft = mPartial.fLeft;
    406             DBG_NAV_LOGD("LeftCheck mHitLeft=%d", mHitLeft);
    407         } else if (mHitLeft == INT_MAX)
    408             return; // wait for intersect success
    409         /* If text is too far away vertically, don't consider it */
    410         if (!mBounds.isEmpty() && (mPartial.fTop > mBounds.fBottom + SLOP
    411                 || mPartial.fBottom < mBounds.fTop - SLOP)) {
    412             DBG_NAV_LOGD("LeftCheck stop mPartial=(%d, %d, %d, %d)"
    413                 " mBounds=(%d, %d, %d, %d)",
    414                 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom,
    415                 mBounds.fLeft, mBounds.fTop, mBounds.fRight, mBounds.fBottom);
    416             mHitLeft = INT_MAX; // and disable future comparisons
    417             return;
    418         }
    419         /* If the considered text is completely to the left or right of the
    420            touch coordinates, skip it, turn off further detection */
    421         if (mPartial.fLeft > mX || mPartial.fRight < mX) {
    422             DBG_NAV_LOGD("LeftCheck stop mX=%d mPartial=(%d, %d, %d, %d)", mX,
    423                 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom);
    424             mHitLeft = INT_MAX;
    425             return;
    426         }
    427         /* record the smallest margins on the left and right */
    428         if (mMostLeft > mPartial.fLeft) {
    429             DBG_NAV_LOGD("LeftCheck new mMostLeft=%d (old=%d)", mPartial.fLeft,
    430                 mMostLeft);
    431             mMostLeft = mPartial.fLeft;
    432         }
    433         if (mBounds.isEmpty())
    434             mBounds = mPartial;
    435         else if (mPartial.fBottom > mBounds.fBottom) {
    436             DBG_NAV_LOGD("LeftCheck new bottom=%d (old=%d)", mPartial.fBottom,
    437                 mBounds.fBottom);
    438             mBounds.fBottom = mPartial.fBottom;
    439         }
    440     }
    441 
    442     static const int HIT_SLOP = 5; // space between text parts and lines
    443     static const int SLOP = 30; // space between text parts and lines
    444     /* const */ SkIRect mHit; // sloppy hit rectangle
    445     SkIRect mBounds; // reference bounds
    446     SkIRect mPartial; // accumulated text bounds, per line
    447     const int mX; // touch location
    448     const int mY;
    449     int mHitLeft; // touched text extremes
    450     int mMostLeft; // paragraph extremes
    451     Type mPartialType;
    452 };
    453 
    454 /*
    455 CenterCheck examines the text in a picture, within a viewable rectangle,
    456 and returns via center() the optimal amount to scroll in x to display the
    457 paragraph of text.
    458 
    459 The caller of CenterCheck has configured (but not allocated) a bitmap
    460 the height and three times the width of the view. The picture is drawn centered
    461 in the bitmap, so text that would be revealed, if the view was scrolled up to
    462 a view-width to the left or right, is considered.
    463 */
    464 class CenterCheck : public CommonCheck {
    465 public:
    466     CenterCheck(int x, int y, int width) : mX(x), mY(y),
    467             mHitLeft(x), mHitRight(x), mMostLeft(INT_MAX), mMostRight(-INT_MAX),
    468             mViewLeft(width), mViewRight(width << 1) {
    469         mHit.set(x - CENTER_SLOP, y - CENTER_SLOP,
    470             x + CENTER_SLOP, y + CENTER_SLOP);
    471         mPartial.setEmpty();
    472     }
    473 
    474     int center() {
    475         doRect(); // process the final line of text
    476         /* If the touch coordinates aren't near any text, return 0 */
    477         if (mHitLeft == mHitRight) {
    478             DBG_NAV_LOGD("abort: mHitLeft=%d ==mHitRight", mHitLeft);
    479             return 0;
    480         }
    481         int leftOver = mHitLeft - mViewLeft;
    482         int rightOver = mHitRight - mViewRight;
    483         int center;
    484         /* If the touched text is too large to entirely fit on the screen,
    485            center it. */
    486         if (leftOver < 0 && rightOver > 0) {
    487             center = (leftOver + rightOver) >> 1;
    488             DBG_NAV_LOGD("overlap: leftOver=%d rightOver=%d center=%d",
    489                 leftOver, rightOver, center);
    490             return center;
    491         }
    492         center = (mMostLeft + mMostRight) >> 1; // the paragraph center
    493         if (leftOver > 0 && rightOver >= 0) { // off to the right
    494             if (center > mMostLeft) // move to center loses left-most text?
    495                 center = mMostLeft;
    496         } else if (rightOver < 0 && leftOver <= 0) { // off to the left
    497             if (center < mMostRight) // move to center loses right-most text?
    498                 center = mMostRight;
    499         } else {
    500 #ifdef DONT_CENTER_IF_ALREADY_VISIBLE
    501             center = 0; // paragraph is already fully visible
    502 #endif
    503         }
    504         DBG_NAV_LOGD("scroll: leftOver=%d rightOver=%d center=%d",
    505             leftOver, rightOver, center);
    506         return center;
    507     }
    508 
    509 protected:
    510     virtual bool onIRect(const SkIRect& rect) {
    511         if (joinGlyphs(rect)) // assembles glyphs into a text string
    512             return false;
    513         if (!isTextType(mType))
    514             return false;
    515         /* Text on one line may be broken into several parts. Reassemble
    516            the text into a rectangle before considering it. */
    517         if (rect.fTop < mPartial.fBottom && rect.fBottom >
    518                 mPartial.fTop && mPartial.fRight + CENTER_SLOP >= rect.fLeft) {
    519             DBG_NAV_LOGD("join mPartial=(%d, %d, %d, %d) rect=(%d, %d, %d, %d)",
    520                 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom,
    521                 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
    522             mPartial.join(rect);
    523             return false;
    524         }
    525         if (mPartial.isEmpty() == false)
    526             doRect(); // process the previous line of text
    527         mPartial = rect;
    528         return false;
    529     }
    530 
    531     void doRect()
    532     {
    533         /* Record the outer bounds of the lines of text that was 'hit' by the
    534            touch coordinates, given some slop */
    535         if (SkIRect::Intersects(mPartial, mHit)) {
    536             if (mHitLeft > mPartial.fLeft)
    537                 mHitLeft = mPartial.fLeft;
    538             if (mHitRight < mPartial.fRight)
    539                 mHitRight = mPartial.fRight;
    540             DBG_NAV_LOGD("mHitLeft=%d mHitRight=%d", mHitLeft, mHitRight);
    541         }
    542         /* If the considered text is completely to the left or right of the
    543            touch coordinates, skip it */
    544         if (mPartial.fLeft > mX || mPartial.fRight < mX)
    545             return;
    546         int leftOver = mPartial.fLeft - mViewLeft;
    547         int rightOver = mPartial.fRight - mViewRight;
    548         /* If leftOver <= 0, the text starts off the screen.
    549            If rightOver >= 0, the text ends off the screen.
    550         */
    551         if (leftOver <= 0 && rightOver >= 0) // discard wider than screen
    552             return;
    553 #ifdef DONT_CENTER_IF_ALREADY_VISIBLE
    554         if (leftOver > 0 && rightOver < 0)   // discard already visible
    555             return;
    556 #endif
    557         /* record the smallest margins on the left and right */
    558         if (mMostLeft > leftOver)
    559             mMostLeft = leftOver;
    560         if (mMostRight < rightOver)
    561             mMostRight = rightOver;
    562         DBG_NAV_LOGD("leftOver=%d rightOver=%d mMostLeft=%d mMostRight=%d",
    563             leftOver, rightOver, mMostLeft, mMostRight);
    564     }
    565 
    566     static const int CENTER_SLOP = 10; // space between text parts and lines
    567     /* const */ SkIRect mHit; // sloppy hit rectangle
    568     SkIRect mPartial; // accumulated text bounds, per line
    569     const int mX; // touch location
    570     const int mY;
    571     int mHitLeft; // touched text extremes
    572     int mHitRight;
    573     int mMostLeft; // paragraph extremes
    574     int mMostRight;
    575     const int mViewLeft; // middle third of 3x-wide view
    576     const int mViewRight;
    577 };
    578 
    579 class ImageCanvas : public SkCanvas {
    580 public:
    581     ImageCanvas(SkBounder* bounder) : mURI(NULL) {
    582         setBounder(bounder);
    583     }
    584 
    585 // Currently webkit's bitmap draws always seem to be cull'd before this entry
    586 // point is called, so we assume that any bitmap that gets here is inside our
    587 // tiny clip (may not be true in the future)
    588     virtual void commonDrawBitmap(const SkBitmap& bitmap,
    589                               const SkMatrix& , const SkPaint& ) {
    590         SkPixelRef* pixelRef = bitmap.pixelRef();
    591         if (pixelRef != NULL) {
    592             mURI = pixelRef->getURI();
    593         }
    594     }
    595 
    596     const char* mURI;
    597 };
    598 
    599 class ImageCheck : public SkBounder {
    600 public:
    601     virtual bool onIRect(const SkIRect& rect) {
    602         return false;
    603     }
    604 };
    605 
    606 class JiggleCheck : public CommonCheck {
    607 public:
    608     JiggleCheck(int delta, int width) : mDelta(delta), mMaxX(width) {
    609         mMaxJiggle = 0;
    610         mMinX = mMinJiggle = abs(delta);
    611         mMaxWidth = width + mMinX;
    612     }
    613 
    614     int jiggle() {
    615         if (mMinJiggle > mMaxJiggle)
    616             return mDelta;
    617         int avg = (mMinJiggle + mMaxJiggle + 1) >> 1;
    618         return mDelta < 0 ? -avg : avg;
    619     }
    620 
    621     virtual bool onIRect(const SkIRect& rect) {
    622         if (joinGlyphs(rect))
    623             return false;
    624         if (mType != kDrawBitmap_Type && !isTextType(mType))
    625             return false;
    626         int min, max;
    627         if (mDelta < 0) {
    628             min = mMinX - rect.fLeft;
    629             max = mMaxWidth - rect.fRight;
    630         } else {
    631             min = rect.fRight - mMaxX;
    632             max = rect.fLeft;
    633         }
    634         if (min <= 0)
    635             return false;
    636         if (max >= mMinX)
    637             return false;
    638         if (mMinJiggle > min)
    639             mMinJiggle = min;
    640         if (mMaxJiggle < max)
    641             mMaxJiggle = max;
    642         return false;
    643     }
    644 
    645     int mDelta;
    646     int mMaxJiggle;
    647     int mMaxX;
    648     int mMinJiggle;
    649     int mMinX;
    650     int mMaxWidth;
    651 };
    652 
    653 class RingCheck : public CommonCheck {
    654 public:
    655     RingCheck(const WTF::Vector<WebCore::IntRect>& rings,
    656             const WebCore::IntPoint& location) : mSuccess(true) {
    657         const WebCore::IntRect* r;
    658         for (r = rings.begin(); r != rings.end(); r++) {
    659             SkIRect fatter = {r->x(), r->y(), r->right(), r->bottom()};
    660             fatter.inset(-CURSOR_RING_HIT_TEST_RADIUS, -CURSOR_RING_HIT_TEST_RADIUS);
    661             DBG_NAV_LOGD("fat=(%d,%d,r=%d,b=%d)", fatter.fLeft, fatter.fTop,
    662                 fatter.fRight, fatter.fBottom);
    663             mRings.op(fatter, SkRegion::kUnion_Op);
    664         }
    665         DBG_NAV_LOGD("translate=(%d,%d)", -location.x(), -location.y());
    666         mRings.translate(-location.x(), -location.y());
    667     }
    668 
    669     virtual bool onIRect(const SkIRect& rect) {
    670         if (mSuccess && mType == kDrawGlyph_Type) {
    671             DBG_NAV_LOGD("contains (%d,%d,r=%d,b=%d) == %s",
    672                 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
    673                 mRings.contains(rect) ? "true" : "false");
    674             mSuccess &= mRings.contains(rect);
    675         }
    676         return false;
    677     }
    678 
    679     bool success() { return mSuccess; }
    680     SkRegion mRings;
    681     bool mSuccess;
    682 };
    683 
    684 bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction direction,
    685     WebCore::IntPoint* scrollPtr, bool findClosest)
    686 {
    687     WebCore::IntRect newOutset;
    688     const CachedNode* newNode = best->mNode;
    689     // see if there's a middle node
    690         // if the middle node is in the visited list,
    691         // or if none was computed and the newNode is in the visited list,
    692         // treat result as NULL
    693     if (newNode != NULL && findClosest) {
    694         if (best->bounds().intersects(mHistory->mPriorBounds) == false &&
    695                 checkBetween(best, direction))
    696             newNode = best->mNode;
    697         if (findClosest && maskIfHidden(best)) {
    698             innerMove(document(), best, direction, scrollPtr, false);
    699             return true;
    700         }
    701         newOutset = newNode->cursorRingBounds(best->mFrame);
    702     }
    703     int delta;
    704     bool newNodeInView = scrollDelta(newOutset, direction, &delta);
    705     if (delta && scrollPtr && (newNode == NULL || newNodeInView == false ||
    706             (best->mNavOutside && best->mWorkingOutside)))
    707         *scrollPtr = WebCore::IntPoint(direction & UP_DOWN ? 0 : delta,
    708             direction & UP_DOWN ? delta : 0);
    709     return false;
    710 }
    711 
    712 
    713 int CachedRoot::checkForCenter(int x, int y) const
    714 {
    715     int width = mViewBounds.width();
    716     CenterCheck centerCheck(x + width - mViewBounds.x(), y - mViewBounds.y(),
    717         width);
    718     BoundsCanvas checker(&centerCheck);
    719     SkBitmap bitmap;
    720     bitmap.setConfig(SkBitmap::kARGB_8888_Config, width * 3,
    721         mViewBounds.height());
    722     checker.setBitmapDevice(bitmap);
    723     checker.translate(SkIntToScalar(width - mViewBounds.x()),
    724         SkIntToScalar(-mViewBounds.y()));
    725     checker.drawPicture(*pictureAt(x, y));
    726     return centerCheck.center();
    727 }
    728 
    729 void CachedRoot::checkForJiggle(int* xDeltaPtr) const
    730 {
    731     int xDelta = *xDeltaPtr;
    732     JiggleCheck jiggleCheck(xDelta, mViewBounds.width());
    733     BoundsCanvas checker(&jiggleCheck);
    734     SkBitmap bitmap;
    735     int absDelta = abs(xDelta);
    736     bitmap.setConfig(SkBitmap::kARGB_8888_Config, mViewBounds.width() +
    737         absDelta, mViewBounds.height());
    738     checker.setBitmapDevice(bitmap);
    739     int x = -mViewBounds.x() - (xDelta < 0 ? xDelta : 0);
    740     int y = -mViewBounds.y();
    741     checker.translate(SkIntToScalar(x), SkIntToScalar(y));
    742     checker.drawPicture(*pictureAt(x, y));
    743     *xDeltaPtr = jiggleCheck.jiggle();
    744 }
    745 
    746 bool CachedRoot::checkRings(SkPicture* picture,
    747         const WTF::Vector<WebCore::IntRect>& rings,
    748         const WebCore::IntRect& bounds) const
    749 {
    750     if (!picture)
    751         return false;
    752     RingCheck ringCheck(rings, bounds.location());
    753     BoundsCanvas checker(&ringCheck);
    754     SkBitmap bitmap;
    755     bitmap.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(),
    756         bounds.height());
    757     checker.setBitmapDevice(bitmap);
    758     checker.translate(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y()));
    759     checker.drawPicture(*picture);
    760     DBG_NAV_LOGD("bounds=(%d,%d,r=%d,b=%d) success=%s",
    761         bounds.x(), bounds.y(), bounds.right(), bounds.bottom(),
    762         ringCheck.success() ? "true" : "false");
    763     return ringCheck.success();
    764 }
    765 
    766 void CachedRoot::draw(FindCanvas& canvas) const
    767 {
    768     canvas.setLayerId(-1); // overlays change the ID as their pictures draw
    769     canvas.drawPicture(*mPicture);
    770 #if USE(ACCELERATED_COMPOSITING)
    771     if (!mRootLayer)
    772         return;
    773     canvas.drawLayers(mRootLayer);
    774 #endif
    775 }
    776 
    777 const CachedNode* CachedRoot::findAt(const WebCore::IntRect& rect,
    778     const CachedFrame** framePtr, int* x, int* y, bool checkForHidden) const
    779 {
    780     int best = INT_MAX;
    781     bool inside = false;
    782     (const_cast<CachedRoot*>(this))->resetClippedOut();
    783     const CachedFrame* directHitFramePtr;
    784     const CachedNode* directHit = NULL;
    785     const CachedNode* node = findBestAt(rect, &best, &inside, &directHit,
    786         &directHitFramePtr, framePtr, x, y, checkForHidden);
    787     DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(),
    788         node == NULL ? NULL : node->nodePointer());
    789     if (node == NULL) {
    790         node = findBestHitAt(rect, framePtr, x, y);
    791         DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(),
    792             node == NULL ? NULL : node->nodePointer());
    793     }
    794     if (node == NULL) {
    795         *framePtr = findBestFrameAt(rect.x() + (rect.width() >> 1),
    796             rect.y() + (rect.height() >> 1));
    797     }
    798     return node;
    799 }
    800 
    801 WebCore::IntPoint CachedRoot::cursorLocation() const
    802 {
    803     const WebCore::IntRect& bounds = mHistory->mNavBounds;
    804     return WebCore::IntPoint(bounds.x() + (bounds.width() >> 1),
    805         bounds.y() + (bounds.height() >> 1));
    806 }
    807 
    808 WebCore::IntPoint CachedRoot::focusLocation() const
    809 {
    810     return WebCore::IntPoint(mFocusBounds.x() + (mFocusBounds.width() >> 1),
    811         mFocusBounds.y() + (mFocusBounds.height() >> 1));
    812 }
    813 
    814 // These reset the values because we only want to get the selection the first time.
    815 // After that, the selection is no longer accurate.
    816 int CachedRoot::getAndResetSelectionEnd()
    817 {
    818     int end = mSelectionEnd;
    819     mSelectionEnd = -1;
    820     return end;
    821 }
    822 
    823 int CachedRoot::getAndResetSelectionStart()
    824 {
    825     int start = mSelectionStart;
    826     mSelectionStart = -1;
    827     return start;
    828 }
    829 
    830 int CachedRoot::getBlockLeftEdge(int x, int y, float scale) const
    831 {
    832     DBG_NAV_LOGD("x=%d y=%d scale=%g mViewBounds=(%d,%d,%d,%d)", x, y, scale,
    833         mViewBounds.x(), mViewBounds.y(), mViewBounds.width(),
    834         mViewBounds.height());
    835     // if (x, y) is in a textArea or textField, return that
    836     const int slop = 1;
    837     WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop,
    838         slop * 2, slop * 2);
    839     const CachedFrame* frame;
    840     int fx, fy;
    841     const CachedNode* node = findAt(rect, &frame, &fx, &fy, true);
    842     if (node && node->wantsKeyEvents()) {
    843         DBG_NAV_LOGD("x=%d (%s)", node->bounds(frame).x(),
    844             node->isTextInput() ? "text" : "plugin");
    845         return node->bounds(frame).x();
    846     }
    847     SkPicture* picture = node ? frame->picture(node) : pictureAt(x, y);
    848     if (!picture)
    849         return x;
    850     int halfW = (int) (mViewBounds.width() * scale * 0.5f);
    851     int fullW = halfW << 1;
    852     int halfH = (int) (mViewBounds.height() * scale * 0.5f);
    853     int fullH = halfH << 1;
    854     LeftCheck leftCheck(fullW, halfH);
    855     BoundsCanvas checker(&leftCheck);
    856     SkBitmap bitmap;
    857     bitmap.setConfig(SkBitmap::kARGB_8888_Config, fullW, fullH);
    858     checker.setBitmapDevice(bitmap);
    859     checker.translate(SkIntToScalar(fullW - x), SkIntToScalar(halfH - y));
    860     checker.drawPicture(*picture);
    861     int result = x + leftCheck.left() - fullW;
    862     DBG_NAV_LOGD("halfW=%d halfH=%d mMostLeft=%d x=%d",
    863         halfW, halfH, leftCheck.mMostLeft, result);
    864     return result;
    865 }
    866 
    867 void CachedRoot::getSimulatedMousePosition(WebCore::IntPoint* point) const
    868 {
    869 #ifndef NDEBUG
    870     ASSERT(CachedFrame::mDebug.mInUse);
    871 #endif
    872     const WebCore::IntRect& mouseBounds = mHistory->mMouseBounds;
    873     int x = mouseBounds.x();
    874     int y = mouseBounds.y();
    875     int width = mouseBounds.width();
    876     int height = mouseBounds.height();
    877     point->setX(x + (width >> 1)); // default to box center
    878     point->setY(y + (height >> 1));
    879 #if DEBUG_NAV_UI
    880     const WebCore::IntRect& navBounds = mHistory->mNavBounds;
    881     DBG_NAV_LOGD("mHistory->mNavBounds={%d,%d,%d,%d} "
    882         "mHistory->mMouseBounds={%d,%d,%d,%d} point={%d,%d}",
    883         navBounds.x(), navBounds.y(), navBounds.width(), navBounds.height(),
    884         mouseBounds.x(), mouseBounds.y(), mouseBounds.width(),
    885         mouseBounds.height(), point->x(), point->y());
    886 #endif
    887 }
    888 
    889 void CachedRoot::init(WebCore::Frame* frame, CachedHistory* history)
    890 {
    891     CachedFrame::init(this, -1, frame);
    892     reset();
    893     mHistory = history;
    894     mPicture = NULL;
    895 }
    896 
    897 bool CachedRoot::innerDown(const CachedNode* test, BestData* bestData) const
    898 {
    899     ASSERT(minWorkingVertical() >= mViewBounds.x());
    900     ASSERT(maxWorkingVertical() <= mViewBounds.right());
    901     setupScrolledBounds();
    902     // (line up)
    903     mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll);
    904     int testTop = mScrolledBounds.y();
    905     int viewBottom = mViewBounds.bottom();
    906     const WebCore::IntRect& navBounds = mHistory->mNavBounds;
    907     if (navBounds.isEmpty() == false &&
    908             navBounds.bottom() > viewBottom && viewBottom < mContents.height())
    909         return false;
    910     if (navBounds.isEmpty() == false) {
    911         int navTop = navBounds.y();
    912         int scrollBottom;
    913         if (testTop < navTop && navTop < (scrollBottom = mScrolledBounds.bottom())) {
    914             mScrolledBounds.setHeight(scrollBottom - navTop);
    915             mScrolledBounds.setY(navTop);
    916         }
    917     }
    918     setCursorCache(0, mMaxYScroll);
    919     frameDown(test, NULL, bestData);
    920     return true;
    921 }
    922 
    923 bool CachedRoot::innerLeft(const CachedNode* test, BestData* bestData) const
    924 {
    925     ASSERT(minWorkingHorizontal() >= mViewBounds.y());
    926     ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom());
    927     setupScrolledBounds();
    928     mScrolledBounds.setX(mScrolledBounds.x() - mMaxXScroll);
    929     mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll);
    930     int testRight = mScrolledBounds.right();
    931     int viewLeft = mViewBounds.x();
    932     const WebCore::IntRect& navBounds = mHistory->mNavBounds;
    933     if (navBounds.isEmpty() == false &&
    934             navBounds.x() < viewLeft && viewLeft > mContents.x())
    935         return false;
    936     if (navBounds.isEmpty() == false) {
    937         int navRight = navBounds.right();
    938         int scrollLeft;
    939         if (testRight > navRight && navRight > (scrollLeft = mScrolledBounds.x()))
    940             mScrolledBounds.setWidth(navRight - scrollLeft);
    941     }
    942     setCursorCache(-mMaxXScroll, 0);
    943     frameLeft(test, NULL, bestData);
    944     return true;
    945 }
    946 
    947 
    948 void CachedRoot::innerMove(const CachedNode* node, BestData* bestData,
    949     Direction direction, WebCore::IntPoint* scroll, bool firstCall)
    950 {
    951     bestData->reset();
    952     bool outOfCursor = mCursorIndex == CURSOR_CLEARED;
    953     DBG_NAV_LOGD("mHistory->didFirstLayout()=%s && mCursorIndex=%d",
    954         mHistory->didFirstLayout() ? "true" : "false", mCursorIndex);
    955     if (mHistory->didFirstLayout() && mCursorIndex < CURSOR_SET) {
    956         mHistory->reset();
    957         outOfCursor = true;
    958     }
    959     const CachedFrame* cursorFrame;
    960     const CachedNode* cursor = currentCursor(&cursorFrame);
    961     mHistory->setWorking(direction, cursorFrame, cursor, mViewBounds);
    962     bool findClosest = false;
    963     if (mScrollOnly == false) {
    964         switch (direction) {
    965             case LEFT:
    966                 if (outOfCursor)
    967                     mHistory->mNavBounds = WebCore::IntRect(mViewBounds.right(),
    968                         mViewBounds.y(), 1, mViewBounds.height());
    969                 findClosest = innerLeft(node, bestData);
    970                 break;
    971             case RIGHT:
    972                 if (outOfCursor)
    973                     mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x() - 1,
    974                         mViewBounds.y(), 1, mViewBounds.height());
    975                 findClosest = innerRight(node, bestData);
    976                 break;
    977             case UP:
    978                 if (outOfCursor)
    979                     mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(),
    980                         mViewBounds.bottom(), mViewBounds.width(), 1);
    981                 findClosest = innerUp(node, bestData);
    982                 break;
    983             case DOWN:
    984                 if (outOfCursor)
    985                     mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(),
    986                         mViewBounds.y() - 1, mViewBounds.width(), 1);
    987                 findClosest = innerDown(node, bestData);
    988                 break;
    989             case UNINITIALIZED:
    990             default:
    991                 ASSERT(0);
    992         }
    993     }
    994     if (firstCall)
    995         mHistory->mPriorBounds = mHistory->mNavBounds; // bounds always advances, even if new node is ultimately NULL
    996     bestData->setMouseBounds(bestData->bounds());
    997     if (adjustForScroll(bestData, direction, scroll, findClosest))
    998         return;
    999     if (bestData->mNode != NULL) {
   1000         mHistory->addToVisited(bestData->mNode, direction);
   1001         mHistory->mNavBounds = bestData->bounds();
   1002         mHistory->mMouseBounds = bestData->mouseBounds();
   1003     } else if (scroll->x() != 0 || scroll->y() != 0) {
   1004         WebCore::IntRect newBounds = mHistory->mNavBounds;
   1005         int offsetX = scroll->x();
   1006         int offsetY = scroll->y();
   1007         newBounds.move(offsetX, offsetY);
   1008         if (mViewBounds.x() > newBounds.x())
   1009             offsetX = mViewBounds.x() - mHistory->mNavBounds.x();
   1010         else if (mViewBounds.right() < newBounds.right())
   1011             offsetX = mViewBounds.right() - mHistory->mNavBounds.right();
   1012         if (mViewBounds.y() > newBounds.y())
   1013             offsetY = mViewBounds.y() - mHistory->mNavBounds.y();
   1014         else if (mViewBounds.bottom() < newBounds.bottom())
   1015             offsetY = mViewBounds.bottom() - mHistory->mNavBounds.bottom();
   1016         mHistory->mNavBounds.move(offsetX, offsetY);
   1017     }
   1018     mHistory->setDidFirstLayout(false);
   1019 }
   1020 
   1021 bool CachedRoot::innerRight(const CachedNode* test, BestData* bestData) const
   1022 {
   1023     ASSERT(minWorkingHorizontal() >= mViewBounds.y());
   1024     ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom());
   1025     setupScrolledBounds();
   1026     // (align)
   1027     mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll);
   1028     int testLeft = mScrolledBounds.x();
   1029     int viewRight = mViewBounds.right();
   1030     const WebCore::IntRect& navBounds = mHistory->mNavBounds;
   1031     if (navBounds.isEmpty() == false &&
   1032             navBounds.right() > viewRight && viewRight < mContents.width())
   1033         return false;
   1034     if (navBounds.isEmpty() == false) {
   1035         int navLeft = navBounds.x();
   1036         int scrollRight;
   1037         if (testLeft < navLeft && navLeft < (scrollRight = mScrolledBounds.right())) {
   1038             mScrolledBounds.setWidth(scrollRight - navLeft);
   1039             mScrolledBounds.setX(navLeft);
   1040         }
   1041     }
   1042     setCursorCache(mMaxXScroll, 0);
   1043     frameRight(test, NULL, bestData);
   1044     return true;
   1045 }
   1046 
   1047 bool CachedRoot::innerUp(const CachedNode* test, BestData* bestData) const
   1048 {
   1049     ASSERT(minWorkingVertical() >= mViewBounds.x());
   1050     ASSERT(maxWorkingVertical() <= mViewBounds.right());
   1051     setupScrolledBounds();
   1052     mScrolledBounds.setY(mScrolledBounds.y() - mMaxYScroll);
   1053     mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll);
   1054     int testBottom = mScrolledBounds.bottom();
   1055     int viewTop = mViewBounds.y();
   1056     const WebCore::IntRect& navBounds = mHistory->mNavBounds;
   1057     if (navBounds.isEmpty() == false &&
   1058             navBounds.y() < viewTop && viewTop > mContents.y())
   1059         return false;
   1060     if (navBounds.isEmpty() == false) {
   1061         int navBottom = navBounds.bottom();
   1062         int scrollTop;
   1063         if (testBottom > navBottom && navBottom > (scrollTop = mScrolledBounds.y()))
   1064             mScrolledBounds.setHeight(navBottom - scrollTop);
   1065     }
   1066     setCursorCache(0, -mMaxYScroll);
   1067     frameUp(test, NULL, bestData);
   1068     return true;
   1069 }
   1070 
   1071 WebCore::String CachedRoot::imageURI(int x, int y) const
   1072 {
   1073     ImageCheck imageCheck;
   1074     ImageCanvas checker(&imageCheck);
   1075     SkBitmap bitmap;
   1076     bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
   1077     checker.setBitmapDevice(bitmap);
   1078     checker.translate(SkIntToScalar(-x), SkIntToScalar(-y));
   1079     checker.drawPicture(*pictureAt(x, y));
   1080     return WebCore::String(checker.mURI);
   1081 }
   1082 
   1083 bool CachedRoot::maskIfHidden(BestData* best) const
   1084 {
   1085     const CachedNode* bestNode = best->mNode;
   1086     if (bestNode->isUnclipped() || bestNode->isTransparent())
   1087         return false;
   1088     const CachedFrame* frame = best->mFrame;
   1089     SkPicture* picture = frame->picture(bestNode);
   1090     if (picture == NULL) {
   1091         DBG_NAV_LOG("missing picture");
   1092         return false;
   1093     }
   1094     // given the picture matching this nav cache
   1095         // create an SkBitmap with dimensions of the cursor intersected w/ extended view
   1096     const WebCore::IntRect& nodeBounds = bestNode->bounds(frame);
   1097     WebCore::IntRect bounds = nodeBounds;
   1098     bounds.intersect(mScrolledBounds);
   1099     int leftMargin = bounds.x() == nodeBounds.x() ? kMargin : 0;
   1100     int topMargin = bounds.y() == nodeBounds.y() ? kMargin : 0;
   1101     int rightMargin = bounds.right() == nodeBounds.right() ? kMargin : 0;
   1102     int bottomMargin = bounds.bottom() == nodeBounds.bottom() ? kMargin : 0;
   1103     bool unclipped = (leftMargin & topMargin & rightMargin & bottomMargin) != 0;
   1104     WebCore::IntRect marginBounds = nodeBounds;
   1105     marginBounds.inflate(kMargin);
   1106     marginBounds.intersect(mScrolledBounds);
   1107     SkScalar offsetX = SkIntToScalar(leftMargin - bounds.x());
   1108     SkScalar offsetY = SkIntToScalar(topMargin - bounds.y());
   1109 #if USE(ACCELERATED_COMPOSITING)
   1110     // When cached nodes are constructed in CacheBuilder.cpp, their
   1111     // unclipped attribute is set so this condition won't be reached.
   1112     // In the future, layers may contain nodes that can be clipped.
   1113     // So to be safe, adjust the layer picture by its offset.
   1114     if (bestNode->isInLayer()) {
   1115         const CachedLayer* cachedLayer = frame->layer(bestNode);
   1116         const LayerAndroid* layer = cachedLayer->layer(mRootLayer);
   1117         SkMatrix pictMatrix;
   1118         layer->localToGlobal(&pictMatrix);
   1119         // FIXME: ignore scale, rotation for now
   1120         offsetX += pictMatrix.getTranslateX();
   1121         offsetY += pictMatrix.getTranslateY();
   1122         DBG_NAV_LOGD("layer picture=%p (%g,%g)", picture,
   1123             pictMatrix.getTranslateX(), pictMatrix.getTranslateY());
   1124     }
   1125 #endif
   1126     BoundsCheck boundsCheck;
   1127     BoundsCanvas checker(&boundsCheck);
   1128     boundsCheck.mBounds.set(leftMargin, topMargin,
   1129         leftMargin + bounds.width(), topMargin + bounds.height());
   1130     boundsCheck.mBoundsSlop = boundsCheck.mBounds;
   1131     boundsCheck.mBoundsSlop.inset(-kSlop, -kSlop);
   1132     SkBitmap bitmap;
   1133     bitmap.setConfig(SkBitmap::kARGB_8888_Config, marginBounds.width(),
   1134         marginBounds.height());
   1135     checker.setBitmapDevice(bitmap);
   1136     // insert probes to be called when the data corresponding to this ring is drawn
   1137         // need to know if ring was generated by text, image, or parent (like div)
   1138         // ? need to know (like imdb menu bar) to give up sometimes (when?)
   1139     checker.translate(offsetX, offsetY);
   1140     checker.drawPicture(*picture);
   1141     boundsCheck.checkLast();
   1142     // was it not drawn or clipped out?
   1143     CachedNode* node = const_cast<CachedNode*>(best->mNode);
   1144     if (boundsCheck.hidden()) { // if hidden, return false so that nav can try again
   1145 #if DEBUG_NAV_UI
   1146         const SkIRect& m = boundsCheck.mBounds;
   1147         const SkIRect& s = boundsCheck.mBoundsSlop;
   1148         DBG_NAV_LOGD("hidden node:%p (%d) mBounds={%d,%d,%d,%d} mBoundsSlop="
   1149             "{%d,%d,%d,%d}", node, node->index(),
   1150             m.fLeft, m.fTop, m.fRight, m.fBottom,
   1151             s.fLeft, s.fTop, s.fRight, s.fBottom);
   1152         const SkIRect& o = boundsCheck.mDrawnOver.getBounds();
   1153         const SkIRect& l = boundsCheck.mLastAll;
   1154         const SkIRect& u = boundsCheck.mUnion;
   1155         DBG_NAV_LOGD("hidden mDrawnOver={%d,%d,%d,%d} mLastAll={%d,%d,%d,%d}"
   1156             " mUnion={%d,%d,%d,%d}",
   1157             o.fLeft, o.fTop, o.fRight, o.fBottom,
   1158             l.fLeft, l.fTop, l.fRight, l.fBottom,
   1159             u.fLeft, u.fTop, u.fRight, u.fBottom);
   1160         const SkIRect& a = boundsCheck.mAllDrawnIn;
   1161         const WebCore::IntRect& c = mScrolledBounds;
   1162         const WebCore::IntRect& b = nodeBounds;
   1163         DBG_NAV_LOGD("hidden mAllDrawnIn={%d,%d,%d,%d}"
   1164             " mScrolledBounds={%d,%d,%d,%d} nodeBounds={%d,%d,%d,%d}",
   1165             a.fLeft, a.fTop, a.fRight, a.fBottom,
   1166             c.x(), c.y(), c.right(), c.bottom(),
   1167             b.x(), b.y(), b.right(), b.bottom());
   1168         DBG_NAV_LOGD("bits.mWidth=%d bits.mHeight=%d transX=%d transY=%d",
   1169             marginBounds.width(),marginBounds.height(),
   1170             kMargin - bounds.x(), kMargin - bounds.y());
   1171 #endif
   1172         node->setDisabled(true);
   1173         node->setClippedOut(unclipped == false);
   1174         return true;
   1175     }
   1176     // was it partially occluded by later drawing?
   1177     // if partially occluded, modify the bounds so that the mouse click has a better x,y
   1178        const SkIRect& over = boundsCheck.mDrawnOver.getBounds();
   1179     if (over.isEmpty() == false) {
   1180 #if DEBUG_NAV_UI
   1181         SkIRect orig = boundsCheck.mBounds;
   1182 #endif
   1183         SkIRect& base = boundsCheck.mBounds;
   1184         if (base.fLeft < over.fRight && base.fRight > over.fRight)
   1185             base.fLeft = over.fRight;
   1186         else if (base.fRight > over.fLeft && base.fLeft < over.fLeft)
   1187             base.fRight = over.fLeft;
   1188         if (base.fTop < over.fBottom && base.fBottom > over.fBottom)
   1189             base.fTop = over.fBottom;
   1190         else if (base.fBottom > over.fTop && base.fTop < over.fTop)
   1191             base.fBottom = over.fTop;
   1192 #if DEBUG_NAV_UI
   1193         const SkIRect& modded = boundsCheck.mBounds;
   1194         DBG_NAV_LOGD("partially occluded node:%p (%d) old:{%d,%d,%d,%d}"
   1195             " new:{%d,%d,%d,%d}", node, node->index(),
   1196             orig.fLeft, orig.fTop, orig.fRight, orig.fBottom,
   1197             base.fLeft, base.fTop, base.fRight, base.fBottom);
   1198 #endif
   1199         best->setMouseBounds(WebCore::IntRect(bounds.x() + base.fLeft - kMargin,
   1200             bounds.y() + base.fTop - kMargin, base.width(), base.height()));
   1201         node->clip(best->mouseBounds());
   1202     }
   1203     return false;
   1204 }
   1205 
   1206 const CachedNode* CachedRoot::moveCursor(Direction direction, const CachedFrame** framePtr,
   1207     WebCore::IntPoint* scroll)
   1208 {
   1209 #ifndef NDEBUG
   1210     ASSERT(CachedFrame::mDebug.mInUse);
   1211 #endif
   1212     CachedRoot* frame = this;
   1213     const CachedNode* node = frame->document();
   1214     if (node == NULL)
   1215         return NULL;
   1216     if (mViewBounds.isEmpty())
   1217         return NULL;
   1218     resetClippedOut();
   1219     setData();
   1220     BestData bestData;
   1221     innerMove(node, &bestData, direction, scroll, true);
   1222     // if node is partially or fully concealed by layer, scroll it into view
   1223     if (mRootLayer && bestData.mNode && !bestData.mNode->isInLayer()) {
   1224 #if USE(ACCELERATED_COMPOSITING)
   1225 #if DUMP_NAV_CACHE
   1226         CachedLayer::Debug::printRootLayerAndroid(mRootLayer);
   1227 #endif
   1228         SkIRect original = bestData.mNode->cursorRingBounds(bestData.mFrame);
   1229         DBG_NAV_LOGD("original=(%d,%d,w=%d,h=%d) scroll=(%d,%d)",
   1230             original.fLeft, original.fTop, original.width(), original.height(),
   1231             scroll->x(), scroll->y());
   1232         original.offset(-scroll->x(), -scroll->y());
   1233         SkRegion rings(original);
   1234         SkTDArray<SkRect> region;
   1235         mRootLayer->clipArea(&region);
   1236         SkRegion layers;
   1237         for (int index = 0; index < region.count(); index++) {
   1238             SkIRect enclosing;
   1239             region[index].round(&enclosing);
   1240             rings.op(enclosing, SkRegion::kDifference_Op);
   1241             layers.op(enclosing, SkRegion::kUnion_Op);
   1242         }
   1243         SkIRect layerBounds(layers.getBounds());
   1244         SkIRect ringBounds(rings.getBounds());
   1245         int scrollX = scroll->x();
   1246         int scrollY = scroll->y();
   1247         if (rings.getBounds() != original) {
   1248             int topOverlap = layerBounds.fBottom - original.fTop;
   1249             int bottomOverlap = original.fBottom - layerBounds.fTop;
   1250             int leftOverlap = layerBounds.fRight - original.fLeft;
   1251             int rightOverlap = original.fRight - layerBounds.fLeft;
   1252             if (direction & UP_DOWN) {
   1253                 if (layerBounds.fLeft < original.fLeft && leftOverlap < 0)
   1254                     scroll->setX(leftOverlap);
   1255                 if (original.fRight < layerBounds.fRight && rightOverlap > 0
   1256                         && -leftOverlap > rightOverlap)
   1257                     scroll->setX(rightOverlap);
   1258                 bool topSet = scrollY > topOverlap && (direction == UP
   1259                     || !scrollY);
   1260                 if (topSet)
   1261                     scroll->setY(topOverlap);
   1262                 if (scrollY < bottomOverlap && (direction == DOWN || (!scrollY
   1263                         && (!topSet || -topOverlap > bottomOverlap))))
   1264                     scroll->setY(bottomOverlap);
   1265             } else {
   1266                 if (layerBounds.fTop < original.fTop && topOverlap < 0)
   1267                     scroll->setY(topOverlap);
   1268                 if (original.fBottom < layerBounds.fBottom && bottomOverlap > 0
   1269                         && -topOverlap > bottomOverlap)
   1270                     scroll->setY(bottomOverlap);
   1271                 bool leftSet = scrollX > leftOverlap && (direction == LEFT
   1272                     || !scrollX);
   1273                 if (leftSet)
   1274                     scroll->setX(leftOverlap);
   1275                 if (scrollX < rightOverlap && (direction == RIGHT || (!scrollX
   1276                         && (!leftSet || -leftOverlap > rightOverlap))))
   1277                     scroll->setX(rightOverlap);
   1278            }
   1279             DBG_NAV_LOGD("rings=(%d,%d,w=%d,h=%d) layers=(%d,%d,w=%d,h=%d)"
   1280                 " scroll=(%d,%d)",
   1281                 ringBounds.fLeft, ringBounds.fTop, ringBounds.width(), ringBounds.height(),
   1282                 layerBounds.fLeft, layerBounds.fTop, layerBounds.width(), layerBounds.height(),
   1283                 scroll->x(), scroll->y());
   1284         }
   1285 #endif
   1286     }
   1287     *framePtr = bestData.mFrame;
   1288     return const_cast<CachedNode*>(bestData.mNode);
   1289 }
   1290 
   1291 const CachedNode* CachedRoot::nextTextField(const CachedNode* start,
   1292         const CachedFrame** framePtr) const
   1293 {
   1294     bool startFound = false;
   1295     return CachedFrame::nextTextField(start, framePtr, &startFound);
   1296 }
   1297 
   1298 SkPicture* CachedRoot::pictureAt(int x, int y) const
   1299 {
   1300 #if USE(ACCELERATED_COMPOSITING)
   1301     if (mRootLayer) {
   1302         const LayerAndroid* layer = mRootLayer->find(x, y);
   1303         if (layer) {
   1304             SkPicture* picture = layer->picture();
   1305             if (picture)
   1306                 return picture;
   1307         }
   1308     }
   1309 #endif
   1310     return mPicture;
   1311 }
   1312 
   1313 void CachedRoot::reset()
   1314 {
   1315 #ifndef NDEBUG
   1316     ASSERT(CachedFrame::mDebug.mInUse);
   1317 #endif
   1318     mContents = mViewBounds = WebCore::IntRect(0, 0, 0, 0);
   1319     mMaxXScroll = mMaxYScroll = 0;
   1320     mRootLayer = 0;
   1321     mSelectionStart = mSelectionEnd = -1;
   1322     mScrollOnly = false;
   1323 }
   1324 
   1325 bool CachedRoot::scrollDelta(WebCore::IntRect& newOutset, Direction direction, int* delta)
   1326 {
   1327     switch (direction) {
   1328         case LEFT:
   1329             *delta = -mMaxXScroll;
   1330             return newOutset.x() >= mViewBounds.x();
   1331         case RIGHT:
   1332             *delta = mMaxXScroll;
   1333             return newOutset.right() <= mViewBounds.right();
   1334         case UP:
   1335             *delta = -mMaxYScroll;
   1336             return newOutset.y() >= mViewBounds.y();
   1337         case DOWN:
   1338             *delta = mMaxYScroll;
   1339             return newOutset.bottom() <= mViewBounds.bottom();
   1340         default:
   1341             *delta = 0;
   1342             ASSERT(0);
   1343     }
   1344     return false;
   1345 }
   1346 
   1347 void CachedRoot::setCachedFocus(CachedFrame* frame, CachedNode* node)
   1348 {
   1349     mFocusBounds = WebCore::IntRect(0, 0, 0, 0);
   1350     if (node == NULL)
   1351         return;
   1352     node->setIsFocus(true);
   1353     mFocusBounds = node->bounds(frame);
   1354     frame->setFocusIndex(node - frame->document());
   1355     CachedFrame* parent;
   1356     while ((parent = frame->parent()) != NULL) {
   1357         parent->setFocusIndex(frame->indexInParent());
   1358         frame = parent;
   1359     }
   1360 #if DEBUG_NAV_UI
   1361     const CachedFrame* focusFrame;
   1362     const CachedNode* focus = currentFocus(&focusFrame);
   1363     WebCore::IntRect bounds = WebCore::IntRect(0, 0, 0, 0);
   1364     if (focus)
   1365         bounds = focus->bounds(focusFrame);
   1366     DBG_NAV_LOGD("new focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}",
   1367         focus ? focus->index() : 0,
   1368         focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(),
   1369         bounds.width(), bounds.height());
   1370 #endif
   1371 }
   1372 
   1373 void CachedRoot::setCursor(CachedFrame* frame, CachedNode* node)
   1374 {
   1375 #if DEBUG_NAV_UI
   1376     const CachedFrame* cursorFrame;
   1377     const CachedNode* cursor = currentCursor(&cursorFrame);
   1378     WebCore::IntRect bounds;
   1379     if (cursor)
   1380         bounds = cursor->bounds(cursorFrame);
   1381     DBG_NAV_LOGD("old cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}",
   1382         cursor ? cursor->index() : 0,
   1383         cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(),
   1384         bounds.width(), bounds.height());
   1385 #endif
   1386     clearCursor();
   1387     if (node == NULL)
   1388         return;
   1389     node->setIsCursor(true);
   1390     node->show();
   1391     frame->setCursorIndex(node - frame->document());
   1392     CachedFrame* parent;
   1393     while ((parent = frame->parent()) != NULL) {
   1394         parent->setCursorIndex(frame->indexInParent());
   1395         frame = parent;
   1396     }
   1397 #if DEBUG_NAV_UI
   1398     cursor = currentCursor(&cursorFrame);
   1399     bounds = WebCore::IntRect(0, 0, 0, 0);
   1400     if (cursor)
   1401         bounds = cursor->bounds(cursorFrame);
   1402     DBG_NAV_LOGD("new cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}",
   1403         cursor ? cursor->index() : 0,
   1404         cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(),
   1405         bounds.width(), bounds.height());
   1406 #endif
   1407 }
   1408 
   1409 void CachedRoot::setCursorCache(int scrollX, int scrollY) const
   1410 {
   1411     mCursor = currentCursor();
   1412     if (mCursor)
   1413         mCursorBounds = mCursor->bounds(this);
   1414     if (!mRootLayer)
   1415         return;
   1416     SkRegion baseScrolled(mScrolledBounds);
   1417     mBaseUncovered = SkRegion(mScrolledBounds);
   1418 #if USE(ACCELERATED_COMPOSITING)
   1419 #if DUMP_NAV_CACHE
   1420     CachedLayer::Debug::printRootLayerAndroid(mRootLayer);
   1421 #endif
   1422     SkTDArray<SkRect> region;
   1423     mRootLayer->clipArea(&region);
   1424     WebCore::IntSize offset(
   1425         copysign(min(max(0, mContents.width() - mScrolledBounds.width()),
   1426         abs(scrollX)), scrollX),
   1427         copysign(min(max(0, mContents.height() - mScrolledBounds.height()),
   1428         abs(scrollY)), scrollY));
   1429     bool hasOffset = offset.width() || offset.height();
   1430     // restrict scrollBounds to that which is not under layer
   1431     for (int index = 0; index < region.count(); index++) {
   1432         SkIRect less;
   1433         region[index].round(&less);
   1434         DBG_NAV_LOGD("less=(%d,%d,w=%d,h=%d)", less.fLeft, less.fTop,
   1435             less.width(), less.height());
   1436         mBaseUncovered.op(less, SkRegion::kDifference_Op);
   1437         if (!hasOffset)
   1438             continue;
   1439         less.offset(offset.width(), offset.height());
   1440         baseScrolled.op(less, SkRegion::kDifference_Op);
   1441     }
   1442     if (hasOffset)
   1443         mBaseUncovered.op(baseScrolled, SkRegion::kUnion_Op);
   1444 #endif
   1445 }
   1446 
   1447 #if DUMP_NAV_CACHE
   1448 
   1449 #define DEBUG_PRINT_BOOL(field) \
   1450     DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false")
   1451 
   1452 #define DEBUG_PRINT_RECT(field) \
   1453     { const WebCore::IntRect& r = b->field; \
   1454     DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \
   1455         r.x(), r.y(), r.width(), r.height()); }
   1456 
   1457 CachedRoot* CachedRoot::Debug::base() const {
   1458     CachedRoot* nav = (CachedRoot*) ((char*) this - OFFSETOF(CachedRoot, mDebug));
   1459     return nav;
   1460 }
   1461 
   1462 void CachedRoot::Debug::print() const
   1463 {
   1464 #ifdef DUMP_NAV_CACHE_USING_PRINTF
   1465     gWriteLogMutex.lock();
   1466     ASSERT(gNavCacheLogFile == NULL);
   1467     gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a");
   1468 #endif
   1469     CachedRoot* b = base();
   1470     b->CachedFrame::mDebug.print();
   1471     b->mHistory->mDebug.print(b);
   1472     DUMP_NAV_LOGD("// int mMaxXScroll=%d, mMaxYScroll=%d;\n",
   1473         b->mMaxXScroll, b->mMaxYScroll);
   1474 #ifdef DUMP_NAV_CACHE_USING_PRINTF
   1475     if (gNavCacheLogFile)
   1476         fclose(gNavCacheLogFile);
   1477     gNavCacheLogFile = NULL;
   1478     gWriteLogMutex.unlock();
   1479 #endif
   1480 }
   1481 
   1482 #endif
   1483 
   1484 }
   1485