2 * Copyright 2007, The Android Open Source Project
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #include "CachedPrefix.h"
27 #include "android_graphics.h"
28 #include "CachedHistory.h"
29 #include "CachedInput.h"
30 #include "CachedNode.h"
32 #include "SkBounder.h"
34 #include "SkPixelRef.h"
37 #include "CachedRoot.h"
39 #ifdef DUMP_NAV_CACHE_USING_PRINTF
40 extern android::Mutex gWriteLogMutex;
45 class CommonCheck : public SkBounder {
63 static bool isTextType(Type t) {
64 return t == kDrawPosTextH_Type || t == kDrawText_Type;
67 CommonCheck() : mType(kNo_Type), mAllOpaque(true), mIsOpaque(true) {
71 bool doRect(Type type) {
73 return doIRect(mUnion);
76 bool joinGlyphs(const SkIRect& rect) {
77 bool isGlyph = mType == kDrawGlyph_Type;
83 void setAllOpaque(bool opaque) { mAllOpaque = opaque; }
84 void setEmpty() { mUnion.setEmpty(); }
85 void setIsOpaque(bool opaque) { mIsOpaque = opaque; }
86 void setType(Type type) { mType = type; }
95 static const char* TypeNames[] = {
104 "kDrawPosTextH_Type",
108 "kDrawTextOnPath_Type"
115 class BoundsCheck : public CommonCheck {
118 mAllDrawnIn.setEmpty();
120 mLastOver.setEmpty();
123 static int Area(SkIRect test) {
124 return test.width() * test.height();
128 if (mAllDrawnIn.isEmpty())
130 if (mLastAll.isEmpty() || Area(mLastAll) < Area(mAllDrawnIn)) {
131 mLastAll = mAllDrawnIn;
132 mDrawnOver.setEmpty();
134 mAllDrawnIn.setEmpty();
138 return (mLastAll.isEmpty() && mLastOver.isEmpty()) ||
139 mDrawnOver.contains(mBounds);
142 virtual bool onIRect(const SkIRect& rect) {
143 if (joinGlyphs(rect))
145 bool interestingType = mType == kDrawBitmap_Type ||
146 mType == kDrawRect_Type || isTextType(mType);
147 if (SkIRect::Intersects(mBounds, rect) == false) {
148 DBG_NAV_LOGD("BoundsCheck (no intersect) rect={%d,%d,%d,%d}"
149 " mType=%s", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
155 if (interestingType == false)
157 if (!mDrawnOver.contains(rect) && (mBoundsSlop.contains(rect) ||
158 (mBounds.fLeft == rect.fLeft && mBounds.fRight == rect.fRight &&
159 mBounds.fTop >= rect.fTop && mBounds.fBottom <= rect.fBottom) ||
160 (mBounds.fTop == rect.fTop && mBounds.fBottom == rect.fBottom &&
161 mBounds.fLeft >= rect.fLeft && mBounds.fRight <= rect.fRight))) {
162 mDrawnOver.setEmpty();
163 mAllDrawnIn.join(rect);
164 DBG_NAV_LOGD("BoundsCheck (contains) rect={%d,%d,%d,%d}"
165 " mAllDrawnIn={%d,%d,%d,%d} mType=%s",
166 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
167 mAllDrawnIn.fLeft, mAllDrawnIn.fTop, mAllDrawnIn.fRight,
168 mAllDrawnIn.fBottom, TypeNames[mType]);
171 if (!isTextType(mType)) {
174 // should the opaqueness of the bitmap disallow its ability to draw over?
175 // not sure that this test is needed
176 (mType != kDrawBitmap_Type ||
177 (mIsOpaque && mAllOpaque)) &&
179 mLastAll.isEmpty() == false)
180 mDrawnOver.op(rect, SkRegion::kUnion_Op);
183 // sometimes the text is not drawn entirely inside the cursor area, even though
184 // it is the correct text. Until I figure out why, I allow text drawn at the
185 // end that is not covered up by something else to represent the link
186 // example that triggers this that should be figured out:
187 // http://cdn.labpixies.com/campaigns/blackjack/blackjack.html?lang=en&country=US&libs=assets/feature/core
188 // ( http://tinyurl.com/ywsyzb )
192 const SkIRect& drawnOver = mDrawnOver.getBounds();
193 DBG_NAV_LOGD("(overlaps) rect={%d,%d,%d,%d}"
194 " mDrawnOver={%d,%d,%d,%d} mType=%s mIsOpaque=%s mAllOpaque=%s",
195 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
196 drawnOver.fLeft, drawnOver.fTop, drawnOver.fRight, drawnOver.fBottom,
197 TypeNames[mType], mIsOpaque ? "true" : "false",
198 mAllOpaque ? "true" : "false");
212 class BoundsCanvas : public SkCanvas {
215 BoundsCanvas(CommonCheck* bounder) : mBounder(*bounder) {
216 mTransparentLayer = 0;
220 virtual ~BoundsCanvas() {
224 virtual void drawPaint(const SkPaint& paint) {
225 mBounder.setType(CommonCheck::kDrawPaint_Type);
226 SkCanvas::drawPaint(paint);
229 virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
230 const SkPaint& paint) {
231 mBounder.setType(CommonCheck::kDrawPoints_Type);
232 SkCanvas::drawPoints(mode, count, pts, paint);
235 virtual void drawRect(const SkRect& rect, const SkPaint& paint) {
236 mBounder.setType(CommonCheck::kDrawRect_Type);
237 SkCanvas::drawRect(rect, paint);
240 virtual void drawPath(const SkPath& path, const SkPaint& paint) {
241 mBounder.setType(CommonCheck::kDrawPath_Type);
242 SkCanvas::drawPath(path, paint);
245 virtual void commonDrawBitmap(const SkBitmap& bitmap,
246 const SkMatrix& matrix, const SkPaint& paint) {
247 mBounder.setType(CommonCheck::kDrawBitmap_Type);
248 mBounder.setIsOpaque(bitmap.isOpaque());
249 SkCanvas::commonDrawBitmap(bitmap, matrix, paint);
252 virtual void drawSprite(const SkBitmap& bitmap, int left, int top,
253 const SkPaint* paint = NULL) {
254 mBounder.setType(CommonCheck::kDrawSprite_Type);
255 mBounder.setIsOpaque(bitmap.isOpaque());
256 SkCanvas::drawSprite(bitmap, left, top, paint);
259 virtual void drawText(const void* text, size_t byteLength, SkScalar x,
260 SkScalar y, const SkPaint& paint) {
262 mBounder.setType(CommonCheck::kDrawGlyph_Type);
263 SkCanvas::drawText(text, byteLength, x, y, paint);
264 mBounder.doRect(CommonCheck::kDrawText_Type);
267 virtual void drawPosText(const void* text, size_t byteLength,
268 const SkPoint pos[], const SkPaint& paint) {
270 mBounder.setType(CommonCheck::kDrawGlyph_Type);
271 SkCanvas::drawPosText(text, byteLength, pos, paint);
272 mBounder.doRect(CommonCheck::kDrawPosText_Type);
275 virtual void drawPosTextH(const void* text, size_t byteLength,
276 const SkScalar xpos[], SkScalar constY,
277 const SkPaint& paint) {
279 mBounder.setType(CommonCheck::kDrawGlyph_Type);
280 SkCanvas::drawPosTextH(text, byteLength, xpos, constY, paint);
281 if (mBounder.mUnion.isEmpty())
283 SkPaint::FontMetrics metrics;
284 paint.getFontMetrics(&metrics);
285 SkPoint upDown[2] = { {xpos[0], constY + metrics.fAscent},
286 {xpos[0], constY + metrics.fDescent} };
287 const SkMatrix& matrix = getTotalMatrix();
288 matrix.mapPoints(upDown, 2);
289 if (upDown[0].fX == upDown[1].fX) {
290 mBounder.mUnion.fTop = SkScalarFloor(upDown[0].fY);
291 mBounder.mUnion.fBottom = SkScalarFloor(upDown[1].fY);
293 mBounder.doRect(CommonCheck::kDrawPosTextH_Type);
296 virtual void drawTextOnPath(const void* text, size_t byteLength,
297 const SkPath& path, const SkMatrix* matrix,
298 const SkPaint& paint) {
300 mBounder.setType(CommonCheck::kDrawGlyph_Type);
301 SkCanvas::drawTextOnPath(text, byteLength, path, matrix, paint);
302 mBounder.doRect(CommonCheck::kDrawTextOnPath_Type);
305 virtual void drawPicture(SkPicture& picture) {
306 mBounder.setType(CommonCheck::kDrawPicture_Type);
307 SkCanvas::drawPicture(picture);
310 virtual int saveLayer(const SkRect* bounds, const SkPaint* paint,
312 int depth = SkCanvas::saveLayer(bounds, paint, flags);
313 if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) {
314 mTransparentLayer = depth;
315 mBounder.setAllOpaque(false);
320 virtual void restore() {
321 int depth = getSaveCount();
322 if (depth == mTransparentLayer) {
323 mTransparentLayer = 0;
324 mBounder.setAllOpaque(true);
329 int mTransparentLayer;
330 CommonCheck& mBounder;
334 LeftCheck examines the text in a picture, within a viewable rectangle,
335 and returns via left() the position of the left edge of the paragraph.
336 It first looks at the left edge of the test point, then looks above and below
337 it for more lines of text to determine the div's left edge.
339 class LeftCheck : public CommonCheck {
341 LeftCheck(int x, int y) : mX(x), mY(y), mHitLeft(INT_MAX),
343 mHit.set(x - (HIT_SLOP << 1), y - HIT_SLOP, x, y + HIT_SLOP);
346 mPartialType = kNo_Type;
350 if (isTextType(mType))
351 doRect(); // process the final line of text
352 return mMostLeft != INT_MAX ? mMostLeft : mX >> 1;
355 // FIXME: this is identical to CenterCheck::onIRect()
356 // refactor so that LeftCheck and CenterCheck inherit common functions
357 virtual bool onIRect(const SkIRect& rect) {
358 bool opaqueBitmap = mType == kDrawBitmap_Type && mIsOpaque;
359 if (opaqueBitmap && rect.contains(mX, mY)) {
360 mMostLeft = rect.fLeft;
363 if (joinGlyphs(rect)) // assembles glyphs into a text string
365 if (!isTextType(mType) && !opaqueBitmap)
367 /* Text on one line may be broken into several parts. Reassemble
368 the text into a rectangle before considering it. */
369 if (rect.fTop < mPartial.fBottom
370 && rect.fBottom > mPartial.fTop
371 && mPartial.fRight + SLOP >= rect.fLeft
372 && (mPartialType != kDrawBitmap_Type
373 || mPartial.height() <= rect.height() + HIT_SLOP)) {
374 DBG_NAV_LOGD("LeftCheck join mPartial=(%d, %d, %d, %d)"
375 " rect=(%d, %d, %d, %d)",
376 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom,
377 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
381 if (mPartial.isEmpty() == false) {
382 doRect(); // process the previous line of text
384 if (mHitLeft == INT_MAX)
385 DBG_NAV_LOGD("LeftCheck disabled rect=(%d, %d, %d, %d)",
386 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
390 mPartialType = mType;
396 /* Record the outer bounds of the lines of text that intersect the
397 touch coordinates, given some slop */
398 if (SkIRect::Intersects(mPartial, mHit)) {
399 if (mHitLeft > mPartial.fLeft)
400 mHitLeft = mPartial.fLeft;
401 DBG_NAV_LOGD("LeftCheck mHitLeft=%d", mHitLeft);
402 } else if (mHitLeft == INT_MAX)
403 return; // wait for intersect success
404 /* If text is too far away vertically, don't consider it */
405 if (!mBounds.isEmpty() && (mPartial.fTop > mBounds.fBottom + SLOP
406 || mPartial.fBottom < mBounds.fTop - SLOP)) {
407 DBG_NAV_LOGD("LeftCheck stop mPartial=(%d, %d, %d, %d)"
408 " mBounds=(%d, %d, %d, %d)",
409 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom,
410 mBounds.fLeft, mBounds.fTop, mBounds.fRight, mBounds.fBottom);
411 mHitLeft = INT_MAX; // and disable future comparisons
414 /* If the considered text is completely to the left or right of the
415 touch coordinates, skip it, turn off further detection */
416 if (mPartial.fLeft > mX || mPartial.fRight < mX) {
417 DBG_NAV_LOGD("LeftCheck stop mX=%d mPartial=(%d, %d, %d, %d)", mX,
418 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom);
422 /* record the smallest margins on the left and right */
423 if (mMostLeft > mPartial.fLeft) {
424 DBG_NAV_LOGD("LeftCheck new mMostLeft=%d (old=%d)", mPartial.fLeft,
426 mMostLeft = mPartial.fLeft;
428 if (mBounds.isEmpty())
430 else if (mPartial.fBottom > mBounds.fBottom) {
431 DBG_NAV_LOGD("LeftCheck new bottom=%d (old=%d)", mPartial.fBottom,
433 mBounds.fBottom = mPartial.fBottom;
437 static const int HIT_SLOP = 5; // space between text parts and lines
438 static const int SLOP = 30; // space between text parts and lines
439 /* const */ SkIRect mHit; // sloppy hit rectangle
440 SkIRect mBounds; // reference bounds
441 SkIRect mPartial; // accumulated text bounds, per line
442 const int mX; // touch location
444 int mHitLeft; // touched text extremes
445 int mMostLeft; // paragraph extremes
450 CenterCheck examines the text in a picture, within a viewable rectangle,
451 and returns via center() the optimal amount to scroll in x to display the
454 The caller of CenterCheck has configured (but not allocated) a bitmap
455 the height and three times the width of the view. The picture is drawn centered
456 in the bitmap, so text that would be revealed, if the view was scrolled up to
457 a view-width to the left or right, is considered.
459 class CenterCheck : public CommonCheck {
461 CenterCheck(int x, int y, int width) : mX(x), mY(y),
462 mHitLeft(x), mHitRight(x), mMostLeft(INT_MAX), mMostRight(-INT_MAX),
463 mViewLeft(width), mViewRight(width << 1) {
464 mHit.set(x - CENTER_SLOP, y - CENTER_SLOP,
465 x + CENTER_SLOP, y + CENTER_SLOP);
470 doRect(); // process the final line of text
471 /* If the touch coordinates aren't near any text, return 0 */
472 if (mHitLeft == mHitRight) {
473 DBG_NAV_LOGD("abort: mHitLeft=%d ==mHitRight", mHitLeft);
476 int leftOver = mHitLeft - mViewLeft;
477 int rightOver = mHitRight - mViewRight;
479 /* If the touched text is too large to entirely fit on the screen,
481 if (leftOver < 0 && rightOver > 0) {
482 center = (leftOver + rightOver) >> 1;
483 DBG_NAV_LOGD("overlap: leftOver=%d rightOver=%d center=%d",
484 leftOver, rightOver, center);
487 center = (mMostLeft + mMostRight) >> 1; // the paragraph center
488 if (leftOver > 0 && rightOver >= 0) { // off to the right
489 if (center > mMostLeft) // move to center loses left-most text?
491 } else if (rightOver < 0 && leftOver <= 0) { // off to the left
492 if (center < mMostRight) // move to center loses right-most text?
495 #ifdef DONT_CENTER_IF_ALREADY_VISIBLE
496 center = 0; // paragraph is already fully visible
499 DBG_NAV_LOGD("scroll: leftOver=%d rightOver=%d center=%d",
500 leftOver, rightOver, center);
505 virtual bool onIRect(const SkIRect& rect) {
506 if (joinGlyphs(rect)) // assembles glyphs into a text string
508 if (!isTextType(mType))
510 /* Text on one line may be broken into several parts. Reassemble
511 the text into a rectangle before considering it. */
512 if (rect.fTop < mPartial.fBottom && rect.fBottom >
513 mPartial.fTop && mPartial.fRight + CENTER_SLOP >= rect.fLeft) {
514 DBG_NAV_LOGD("join mPartial=(%d, %d, %d, %d) rect=(%d, %d, %d, %d)",
515 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom,
516 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
520 if (mPartial.isEmpty() == false)
521 doRect(); // process the previous line of text
528 /* Record the outer bounds of the lines of text that was 'hit' by the
529 touch coordinates, given some slop */
530 if (SkIRect::Intersects(mPartial, mHit)) {
531 if (mHitLeft > mPartial.fLeft)
532 mHitLeft = mPartial.fLeft;
533 if (mHitRight < mPartial.fRight)
534 mHitRight = mPartial.fRight;
535 DBG_NAV_LOGD("mHitLeft=%d mHitRight=%d", mHitLeft, mHitRight);
537 /* If the considered text is completely to the left or right of the
538 touch coordinates, skip it */
539 if (mPartial.fLeft > mX || mPartial.fRight < mX)
541 int leftOver = mPartial.fLeft - mViewLeft;
542 int rightOver = mPartial.fRight - mViewRight;
543 /* If leftOver <= 0, the text starts off the screen.
544 If rightOver >= 0, the text ends off the screen.
546 if (leftOver <= 0 && rightOver >= 0) // discard wider than screen
548 #ifdef DONT_CENTER_IF_ALREADY_VISIBLE
549 if (leftOver > 0 && rightOver < 0) // discard already visible
552 /* record the smallest margins on the left and right */
553 if (mMostLeft > leftOver)
554 mMostLeft = leftOver;
555 if (mMostRight < rightOver)
556 mMostRight = rightOver;
557 DBG_NAV_LOGD("leftOver=%d rightOver=%d mMostLeft=%d mMostRight=%d",
558 leftOver, rightOver, mMostLeft, mMostRight);
561 static const int CENTER_SLOP = 10; // space between text parts and lines
562 /* const */ SkIRect mHit; // sloppy hit rectangle
563 SkIRect mPartial; // accumulated text bounds, per line
564 const int mX; // touch location
566 int mHitLeft; // touched text extremes
568 int mMostLeft; // paragraph extremes
570 const int mViewLeft; // middle third of 3x-wide view
571 const int mViewRight;
574 class ImageCanvas : public SkCanvas {
576 ImageCanvas(SkBounder* bounder) : mURI(NULL) {
580 // Currently webkit's bitmap draws always seem to be cull'd before this entry
581 // point is called, so we assume that any bitmap that gets here is inside our
582 // tiny clip (may not be true in the future)
583 virtual void commonDrawBitmap(const SkBitmap& bitmap,
584 const SkMatrix& , const SkPaint& ) {
585 SkPixelRef* pixelRef = bitmap.pixelRef();
586 if (pixelRef != NULL) {
587 mURI = pixelRef->getURI();
594 class ImageCheck : public SkBounder {
596 virtual bool onIRect(const SkIRect& rect) {
601 class JiggleCheck : public CommonCheck {
603 JiggleCheck(int delta, int width) : mDelta(delta), mMaxX(width) {
605 mMinX = mMinJiggle = abs(delta);
606 mMaxWidth = width + mMinX;
610 if (mMinJiggle > mMaxJiggle)
612 int avg = (mMinJiggle + mMaxJiggle + 1) >> 1;
613 return mDelta < 0 ? -avg : avg;
616 virtual bool onIRect(const SkIRect& rect) {
617 if (joinGlyphs(rect))
619 if (mType != kDrawBitmap_Type && !isTextType(mType))
623 min = mMinX - rect.fLeft;
624 max = mMaxWidth - rect.fRight;
626 min = rect.fRight - mMaxX;
633 if (mMinJiggle > min)
635 if (mMaxJiggle < max)
648 class RingCheck : public CommonCheck {
650 RingCheck(const WTF::Vector<WebCore::IntRect>& rings,
651 const WebCore::IntPoint& location) : mSuccess(true) {
652 const WebCore::IntRect* r;
653 for (r = rings.begin(); r != rings.end(); r++) {
654 SkIRect fatter = {r->x(), r->y(), r->right(), r->bottom()};
655 fatter.inset(-CURSOR_RING_HIT_TEST_RADIUS, -CURSOR_RING_HIT_TEST_RADIUS);
656 DBG_NAV_LOGD("fat=(%d,%d,r=%d,b=%d)", fatter.fLeft, fatter.fTop,
657 fatter.fRight, fatter.fBottom);
658 mRings.op(fatter, SkRegion::kUnion_Op);
660 DBG_NAV_LOGD("translate=(%d,%d)", -location.x(), -location.y());
661 mRings.translate(-location.x(), -location.y());
664 virtual bool onIRect(const SkIRect& rect) {
665 if (mSuccess && mType == kDrawGlyph_Type) {
666 DBG_NAV_LOGD("contains (%d,%d,r=%d,b=%d) == %s",
667 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
668 mRings.contains(rect) ? "true" : "false");
669 mSuccess &= mRings.contains(rect);
674 bool success() { return mSuccess; }
679 bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction direction,
680 WebCore::IntPoint* scrollPtr, bool findClosest)
682 WebCore::IntRect newOutset;
683 const CachedNode* newNode = best->mNode;
684 // see if there's a middle node
685 // if the middle node is in the visited list,
686 // or if none was computed and the newNode is in the visited list,
687 // treat result as NULL
688 if (newNode != NULL && findClosest) {
689 if (best->bounds().intersects(mHistory->mPriorBounds) == false &&
690 checkBetween(best, direction))
691 newNode = best->mNode;
692 if (findClosest && maskIfHidden(best)) {
693 innerMove(document(), best, direction, scrollPtr, false);
696 newNode->cursorRingBounds(&newOutset);
699 bool newNodeInView = scrollDelta(newOutset, direction, &delta);
700 if (delta && scrollPtr && (newNode == NULL || newNodeInView == false ||
701 (best->mNavOutside && best->mWorkingOutside)))
702 *scrollPtr = WebCore::IntPoint(direction & UP_DOWN ? 0 : delta,
703 direction & UP_DOWN ? delta : 0);
708 int CachedRoot::checkForCenter(int x, int y) const
710 int width = mViewBounds.width();
711 CenterCheck centerCheck(x + width - mViewBounds.x(), y - mViewBounds.y(),
713 BoundsCanvas checker(¢erCheck);
715 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width * 3,
716 mViewBounds.height());
717 checker.setBitmapDevice(bitmap);
718 checker.translate(SkIntToScalar(width - mViewBounds.x()),
719 SkIntToScalar(-mViewBounds.y()));
720 checker.drawPicture(*mPicture);
721 return centerCheck.center();
724 void CachedRoot::checkForJiggle(int* xDeltaPtr) const
726 int xDelta = *xDeltaPtr;
727 JiggleCheck jiggleCheck(xDelta, mViewBounds.width());
728 BoundsCanvas checker(&jiggleCheck);
730 int absDelta = abs(xDelta);
731 bitmap.setConfig(SkBitmap::kARGB_8888_Config, mViewBounds.width() +
732 absDelta, mViewBounds.height());
733 checker.setBitmapDevice(bitmap);
734 checker.translate(SkIntToScalar(-mViewBounds.x() -
735 (xDelta < 0 ? xDelta : 0)), SkIntToScalar(-mViewBounds.y()));
736 checker.drawPicture(*mPicture);
737 *xDeltaPtr = jiggleCheck.jiggle();
740 bool CachedRoot::checkRings(const WTF::Vector<WebCore::IntRect>& rings,
741 const WebCore::IntRect& bounds) const
745 RingCheck ringCheck(rings, bounds.location());
746 BoundsCanvas checker(&ringCheck);
748 bitmap.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(),
750 checker.setBitmapDevice(bitmap);
751 checker.translate(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y()));
752 checker.drawPicture(*mPicture);
753 DBG_NAV_LOGD("bounds=(%d,%d,r=%d,b=%d) success=%s",
754 bounds.x(), bounds.y(), bounds.right(), bounds.bottom(),
755 ringCheck.success() ? "true" : "false");
756 return ringCheck.success();
759 const CachedNode* CachedRoot::findAt(const WebCore::IntRect& rect,
760 const CachedFrame** framePtr, int* x, int* y, bool checkForHidden) const
764 (const_cast<CachedRoot*>(this))->resetClippedOut();
765 const CachedNode* directHit = NULL;
766 const CachedNode* node = findBestAt(rect, &best, &inside, &directHit,
767 framePtr, x, y, checkForHidden);
768 DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(),
769 node == NULL ? NULL : node->nodePointer());
771 node = findBestHitAt(rect, framePtr, x, y);
772 DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(),
773 node == NULL ? NULL : node->nodePointer());
776 *framePtr = findBestFrameAt(rect.x() + (rect.width() >> 1),
777 rect.y() + (rect.height() >> 1));
782 WebCore::IntPoint CachedRoot::cursorLocation() const
784 const WebCore::IntRect& bounds = mHistory->mNavBounds;
785 return WebCore::IntPoint(bounds.x() + (bounds.width() >> 1),
786 bounds.y() + (bounds.height() >> 1));
789 WebCore::IntPoint CachedRoot::focusLocation() const
791 return WebCore::IntPoint(mFocusBounds.x() + (mFocusBounds.width() >> 1),
792 mFocusBounds.y() + (mFocusBounds.height() >> 1));
795 // These reset the values because we only want to get the selection the first time.
796 // After that, the selection is no longer accurate.
797 int CachedRoot::getAndResetSelectionEnd()
799 int end = mSelectionEnd;
804 int CachedRoot::getAndResetSelectionStart()
806 int start = mSelectionStart;
807 mSelectionStart = -1;
811 int CachedRoot::getBlockLeftEdge(int x, int y, float scale) const
813 DBG_NAV_LOGD("x=%d y=%d scale=%g mViewBounds=(%d,%d,%d,%d)", x, y, scale,
814 mViewBounds.x(), mViewBounds.y(), mViewBounds.width(),
815 mViewBounds.height());
816 // if (x, y) is in a textArea or textField, return that
818 WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop,
820 const CachedFrame* frame;
822 const CachedNode* node = findAt(rect, &frame, &fx, &fy, true);
823 if (node && node->wantsKeyEvents()) {
824 DBG_NAV_LOGD("x=%d (%s)", node->bounds().x(),
825 node->isTextInput() ? "text" : "plugin");
826 return node->bounds().x();
828 int halfW = (int) (mViewBounds.width() * scale * 0.5f);
829 int fullW = halfW << 1;
830 int halfH = (int) (mViewBounds.height() * scale * 0.5f);
831 int fullH = halfH << 1;
832 LeftCheck leftCheck(fullW, halfH);
833 BoundsCanvas checker(&leftCheck);
835 bitmap.setConfig(SkBitmap::kARGB_8888_Config, fullW, fullH);
836 checker.setBitmapDevice(bitmap);
837 checker.translate(SkIntToScalar(fullW - x), SkIntToScalar(halfH - y));
838 checker.drawPicture(*mPicture);
839 int result = x + leftCheck.left() - fullW;
840 DBG_NAV_LOGD("halfW=%d halfH=%d mMostLeft=%d x=%d",
841 halfW, halfH, leftCheck.mMostLeft, result);
845 void CachedRoot::getSimulatedMousePosition(WebCore::IntPoint* point) const
848 ASSERT(CachedFrame::mDebug.mInUse);
850 const WebCore::IntRect& mouseBounds = mHistory->mMouseBounds;
851 int x = mouseBounds.x();
852 int y = mouseBounds.y();
853 int width = mouseBounds.width();
854 int height = mouseBounds.height();
855 point->setX(x + (width >> 1)); // default to box center
856 point->setY(y + (height >> 1));
858 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
859 DBG_NAV_LOGD("mHistory->mNavBounds={%d,%d,%d,%d} "
860 "mHistory->mMouseBounds={%d,%d,%d,%d} point={%d,%d}",
861 navBounds.x(), navBounds.y(), navBounds.width(), navBounds.height(),
862 mouseBounds.x(), mouseBounds.y(), mouseBounds.width(),
863 mouseBounds.height(), point->x(), point->y());
867 void CachedRoot::init(WebCore::Frame* frame, CachedHistory* history)
869 CachedFrame::init(this, -1, frame);
875 bool CachedRoot::innerDown(const CachedNode* test, BestData* bestData) const
877 ASSERT(minWorkingVertical() >= mViewBounds.x());
878 ASSERT(maxWorkingVertical() <= mViewBounds.right());
879 setupScrolledBounds();
881 mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll);
882 int testTop = mScrolledBounds.y();
883 int viewBottom = mViewBounds.bottom();
884 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
885 if (navBounds.isEmpty() == false &&
886 navBounds.bottom() > viewBottom && viewBottom < mContents.height())
888 if (navBounds.isEmpty() == false) {
889 int navTop = navBounds.y();
891 if (testTop < navTop && navTop < (scrollBottom = mScrolledBounds.bottom())) {
892 mScrolledBounds.setHeight(scrollBottom - navTop);
893 mScrolledBounds.setY(navTop);
896 frameDown(test, NULL, bestData, currentCursor());
900 bool CachedRoot::innerLeft(const CachedNode* test, BestData* bestData) const
902 ASSERT(minWorkingHorizontal() >= mViewBounds.y());
903 ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom());
904 setupScrolledBounds();
905 mScrolledBounds.setX(mScrolledBounds.x() - mMaxXScroll);
906 mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll);
907 int testRight = mScrolledBounds.right();
908 int viewLeft = mViewBounds.x();
909 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
910 if (navBounds.isEmpty() == false &&
911 navBounds.x() < viewLeft && viewLeft > mContents.x())
913 if (navBounds.isEmpty() == false) {
914 int navRight = navBounds.right();
916 if (testRight > navRight && navRight > (scrollLeft = mScrolledBounds.x()))
917 mScrolledBounds.setWidth(navRight - scrollLeft);
919 frameLeft(test, NULL, bestData, currentCursor());
924 void CachedRoot::innerMove(const CachedNode* node, BestData* bestData,
925 Direction direction, WebCore::IntPoint* scroll, bool firstCall)
928 bool outOfCursor = mCursorIndex == CURSOR_CLEARED;
929 DBG_NAV_LOGD("mHistory->didFirstLayout()=%s && mCursorIndex=%d",
930 mHistory->didFirstLayout() ? "true" : "false", mCursorIndex);
931 if (mHistory->didFirstLayout() && mCursorIndex < CURSOR_SET) {
935 const CachedNode* cursor = currentCursor();
936 mHistory->setWorking(direction, cursor, mViewBounds);
937 bool findClosest = false;
938 if (mScrollOnly == false) {
942 mHistory->mNavBounds = WebCore::IntRect(mViewBounds.right(),
943 mViewBounds.y(), 1, mViewBounds.height());
944 findClosest = innerLeft(node, bestData);
948 mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x() - 1,
949 mViewBounds.y(), 1, mViewBounds.height());
950 findClosest = innerRight(node, bestData);
954 mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(),
955 mViewBounds.bottom(), mViewBounds.width(), 1);
956 findClosest = innerUp(node, bestData);
960 mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(),
961 mViewBounds.y() - 1, mViewBounds.width(), 1);
962 findClosest = innerDown(node, bestData);
970 mHistory->mPriorBounds = mHistory->mNavBounds; // bounds always advances, even if new node is ultimately NULL
971 bestData->mMouseBounds = bestData->mNodeBounds;
972 if (adjustForScroll(bestData, direction, scroll, findClosest))
974 if (bestData->mNode != NULL) {
975 mHistory->addToVisited(bestData->mNode, direction);
976 mHistory->mNavBounds = bestData->mNodeBounds;
977 mHistory->mMouseBounds = bestData->mMouseBounds;
978 } else if (scroll->x() != 0 || scroll->y() != 0) {
979 WebCore::IntRect newBounds = mHistory->mNavBounds;
980 int offsetX = scroll->x();
981 int offsetY = scroll->y();
982 newBounds.move(offsetX, offsetY);
983 if (mViewBounds.x() > newBounds.x())
984 offsetX = mViewBounds.x() - mHistory->mNavBounds.x();
985 else if (mViewBounds.right() < newBounds.right())
986 offsetX = mViewBounds.right() - mHistory->mNavBounds.right();
987 if (mViewBounds.y() > newBounds.y())
988 offsetY = mViewBounds.y() - mHistory->mNavBounds.y();
989 else if (mViewBounds.bottom() < newBounds.bottom())
990 offsetY = mViewBounds.bottom() - mHistory->mNavBounds.bottom();
991 mHistory->mNavBounds.move(offsetX, offsetY);
993 mHistory->setDidFirstLayout(false);
996 bool CachedRoot::innerRight(const CachedNode* test, BestData* bestData) const
998 ASSERT(minWorkingHorizontal() >= mViewBounds.y());
999 ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom());
1000 setupScrolledBounds();
1002 mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll);
1003 int testLeft = mScrolledBounds.x();
1004 int viewRight = mViewBounds.right();
1005 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
1006 if (navBounds.isEmpty() == false &&
1007 navBounds.right() > viewRight && viewRight < mContents.width())
1009 if (navBounds.isEmpty() == false) {
1010 int navLeft = navBounds.x();
1012 if (testLeft < navLeft && navLeft < (scrollRight = mScrolledBounds.right())) {
1013 mScrolledBounds.setWidth(scrollRight - navLeft);
1014 mScrolledBounds.setX(navLeft);
1017 frameRight(test, NULL, bestData, currentCursor());
1021 bool CachedRoot::innerUp(const CachedNode* test, BestData* bestData) const
1023 ASSERT(minWorkingVertical() >= mViewBounds.x());
1024 ASSERT(maxWorkingVertical() <= mViewBounds.right());
1025 setupScrolledBounds();
1026 mScrolledBounds.setY(mScrolledBounds.y() - mMaxYScroll);
1027 mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll);
1028 int testBottom = mScrolledBounds.bottom();
1029 int viewTop = mViewBounds.y();
1030 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
1031 if (navBounds.isEmpty() == false &&
1032 navBounds.y() < viewTop && viewTop > mContents.y())
1034 if (navBounds.isEmpty() == false) {
1035 int navBottom = navBounds.bottom();
1037 if (testBottom > navBottom && navBottom > (scrollTop = mScrolledBounds.y()))
1038 mScrolledBounds.setHeight(navBottom - scrollTop);
1040 frameUp(test, NULL, bestData, currentCursor());
1044 WebCore::String CachedRoot::imageURI(int x, int y) const
1046 ImageCheck imageCheck;
1047 ImageCanvas checker(&imageCheck);
1049 bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
1050 checker.setBitmapDevice(bitmap);
1051 checker.translate(SkIntToScalar(-x), SkIntToScalar(-y));
1052 checker.drawPicture(*mPicture);
1053 return WebCore::String(checker.mURI);
1056 bool CachedRoot::maskIfHidden(BestData* best) const
1058 if (mPicture == NULL) {
1059 DBG_NAV_LOG("missing picture");
1062 const CachedNode* bestNode = best->mNode;
1063 if (bestNode->isUnclipped())
1065 // given the picture matching this nav cache
1066 // create an SkBitmap with dimensions of the cursor intersected w/ extended view
1067 const WebCore::IntRect& nodeBounds = bestNode->getBounds();
1068 WebCore::IntRect bounds = nodeBounds;
1069 bounds.intersect(mScrolledBounds);
1070 int leftMargin = bounds.x() == nodeBounds.x() ? kMargin : 0;
1071 int topMargin = bounds.y() == nodeBounds.y() ? kMargin : 0;
1072 int rightMargin = bounds.right() == nodeBounds.right() ? kMargin : 0;
1073 int bottomMargin = bounds.bottom() == nodeBounds.bottom() ? kMargin : 0;
1074 bool unclipped = (leftMargin & topMargin & rightMargin & bottomMargin) != 0;
1075 WebCore::IntRect marginBounds = nodeBounds;
1076 marginBounds.inflate(kMargin);
1077 marginBounds.intersect(mScrolledBounds);
1078 BoundsCheck boundsCheck;
1079 BoundsCanvas checker(&boundsCheck);
1080 boundsCheck.mBounds.set(leftMargin, topMargin,
1081 leftMargin + bounds.width(), topMargin + bounds.height());
1082 boundsCheck.mBoundsSlop = boundsCheck.mBounds;
1083 boundsCheck.mBoundsSlop.inset(-kSlop, -kSlop);
1085 bitmap.setConfig(SkBitmap::kARGB_8888_Config, marginBounds.width(),
1086 marginBounds.height());
1087 checker.setBitmapDevice(bitmap);
1088 // insert probes to be called when the data corresponding to this ring is drawn
1089 // need to know if ring was generated by text, image, or parent (like div)
1090 // ? need to know (like imdb menu bar) to give up sometimes (when?)
1091 checker.translate(SkIntToScalar(leftMargin - bounds.x()),
1092 SkIntToScalar(topMargin - bounds.y()));
1093 checker.drawPicture(*mPicture);
1094 boundsCheck.checkLast();
1095 // was it not drawn or clipped out?
1096 CachedNode* node = const_cast<CachedNode*>(best->mNode);
1097 if (boundsCheck.hidden()) { // if hidden, return false so that nav can try again
1099 const SkIRect& m = boundsCheck.mBounds;
1100 const SkIRect& s = boundsCheck.mBoundsSlop;
1101 DBG_NAV_LOGD("hidden node:%p (%d) mBounds={%d,%d,%d,%d} mBoundsSlop="
1102 "{%d,%d,%d,%d}", node, node->index(),
1103 m.fLeft, m.fTop, m.fRight, m.fBottom,
1104 s.fLeft, s.fTop, s.fRight, s.fBottom);
1105 const SkIRect& o = boundsCheck.mDrawnOver.getBounds();
1106 const SkIRect& l = boundsCheck.mLastAll;
1107 const SkIRect& u = boundsCheck.mUnion;
1108 DBG_NAV_LOGD("hidden mDrawnOver={%d,%d,%d,%d} mLastAll={%d,%d,%d,%d}"
1109 " mUnion={%d,%d,%d,%d}",
1110 o.fLeft, o.fTop, o.fRight, o.fBottom,
1111 l.fLeft, l.fTop, l.fRight, l.fBottom,
1112 u.fLeft, u.fTop, u.fRight, u.fBottom);
1113 const SkIRect& a = boundsCheck.mAllDrawnIn;
1114 const WebCore::IntRect& c = mScrolledBounds;
1115 const WebCore::IntRect& b = nodeBounds;
1116 DBG_NAV_LOGD("hidden mAllDrawnIn={%d,%d,%d,%d}"
1117 " mScrolledBounds={%d,%d,%d,%d} nodeBounds={%d,%d,%d,%d}",
1118 a.fLeft, a.fTop, a.fRight, a.fBottom,
1119 c.x(), c.y(), c.right(), c.bottom(),
1120 b.x(), b.y(), b.right(), b.bottom());
1121 DBG_NAV_LOGD("bits.mWidth=%d bits.mHeight=%d transX=%d transY=%d",
1122 marginBounds.width(),marginBounds.height(),
1123 kMargin - bounds.x(), kMargin - bounds.y());
1125 node->setDisabled(true);
1126 node->setClippedOut(unclipped == false);
1129 // was it partially occluded by later drawing?
1130 // if partially occluded, modify the bounds so that the mouse click has a better x,y
1131 const SkIRect& over = boundsCheck.mDrawnOver.getBounds();
1132 if (over.isEmpty() == false) {
1134 SkIRect orig = boundsCheck.mBounds;
1136 SkIRect& base = boundsCheck.mBounds;
1137 if (base.fLeft < over.fRight && base.fRight > over.fRight)
1138 base.fLeft = over.fRight;
1139 else if (base.fRight > over.fLeft && base.fLeft < over.fLeft)
1140 base.fRight = over.fLeft;
1141 if (base.fTop < over.fBottom && base.fBottom > over.fBottom)
1142 base.fTop = over.fBottom;
1143 else if (base.fBottom > over.fTop && base.fTop < over.fTop)
1144 base.fBottom = over.fTop;
1146 const SkIRect& modded = boundsCheck.mBounds;
1147 DBG_NAV_LOGD("partially occluded node:%p (%d) old:{%d,%d,%d,%d}"
1148 " new:{%d,%d,%d,%d}", node, node->index(),
1149 orig.fLeft, orig.fTop, orig.fRight, orig.fBottom,
1150 base.fLeft, base.fTop, base.fRight, base.fBottom);
1152 best->mMouseBounds = WebCore::IntRect(bounds.x() + base.fLeft - kMargin,
1153 bounds.y() + base.fTop - kMargin, base.width(), base.height());
1154 node->clip(best->mMouseBounds);
1160 const CachedNode* CachedRoot::moveCursor(Direction direction, const CachedFrame** framePtr,
1161 WebCore::IntPoint* scroll)
1164 ASSERT(CachedFrame::mDebug.mInUse);
1166 CachedRoot* frame = this;
1167 const CachedNode* node = frame->document();
1170 if (mViewBounds.isEmpty())
1175 innerMove(node, &bestData, direction, scroll, true);
1176 *framePtr = bestData.mFrame;
1177 return const_cast<CachedNode*>(bestData.mNode);
1180 void CachedRoot::reset()
1183 ASSERT(CachedFrame::mDebug.mInUse);
1185 mContents = mViewBounds = WebCore::IntRect(0, 0, 0, 0);
1186 mMaxXScroll = mMaxYScroll = 0;
1187 mSelectionStart = mSelectionEnd = -1;
1188 mScrollOnly = false;
1191 bool CachedRoot::scrollDelta(WebCore::IntRect& newOutset, Direction direction, int* delta)
1193 switch (direction) {
1195 *delta = -mMaxXScroll;
1196 return newOutset.x() >= mViewBounds.x();
1198 *delta = mMaxXScroll;
1199 return newOutset.right() <= mViewBounds.right();
1201 *delta = -mMaxYScroll;
1202 return newOutset.y() >= mViewBounds.y();
1204 *delta = mMaxYScroll;
1205 return newOutset.bottom() <= mViewBounds.bottom();
1213 void CachedRoot::setCachedFocus(CachedFrame* frame, CachedNode* node)
1215 mFocusBounds = WebCore::IntRect(0, 0, 0, 0);
1218 node->setIsFocus(true);
1219 mFocusBounds = node->bounds();
1220 frame->setFocusIndex(node - frame->document());
1221 CachedFrame* parent;
1222 while ((parent = frame->parent()) != NULL) {
1223 parent->setFocusIndex(frame->indexInParent());
1227 const CachedNode* focus = frame->currentFocus();
1228 WebCore::IntRect bounds = WebCore::IntRect(0, 0, 0, 0);
1230 bounds = focus->bounds();
1231 DBG_NAV_LOGD("new focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}",
1232 focus ? focus->index() : 0,
1233 focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(),
1234 bounds.width(), bounds.height());
1238 void CachedRoot::setCursor(CachedFrame* frame, CachedNode* node)
1241 const CachedNode* cursor = currentCursor();
1242 WebCore::IntRect bounds;
1244 bounds = cursor->bounds();
1245 DBG_NAV_LOGD("old cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}",
1246 cursor ? cursor->index() : 0,
1247 cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(),
1248 bounds.width(), bounds.height());
1253 node->setIsCursor(true);
1255 frame->setCursorIndex(node - frame->document());
1256 CachedFrame* parent;
1257 while ((parent = frame->parent()) != NULL) {
1258 parent->setCursorIndex(frame->indexInParent());
1262 cursor = currentCursor();
1263 bounds = WebCore::IntRect(0, 0, 0, 0);
1265 bounds = cursor->bounds();
1266 DBG_NAV_LOGD("new cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}",
1267 cursor ? cursor->index() : 0,
1268 cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(),
1269 bounds.width(), bounds.height());
1275 #define DEBUG_PRINT_BOOL(field) \
1276 DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false")
1278 #define DEBUG_PRINT_RECT(field) \
1279 { const WebCore::IntRect& r = b->field; \
1280 DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \
1281 r.x(), r.y(), r.width(), r.height()); }
1283 CachedRoot* CachedRoot::Debug::base() const {
1284 CachedRoot* nav = (CachedRoot*) ((char*) this - OFFSETOF(CachedRoot, mDebug));
1288 void CachedRoot::Debug::print() const
1290 #ifdef DUMP_NAV_CACHE_USING_PRINTF
1291 gWriteLogMutex.lock();
1292 ASSERT(gNavCacheLogFile == NULL);
1293 gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a");
1295 CachedRoot* b = base();
1296 b->CachedFrame::mDebug.print();
1297 b->mHistory->mDebug.print(b);
1298 DUMP_NAV_LOGD("// int mMaxXScroll=%d, mMaxYScroll=%d;\n",
1299 b->mMaxXScroll, b->mMaxYScroll);
1300 #ifdef DUMP_NAV_CACHE_USING_PRINTF
1301 if (gNavCacheLogFile)
1302 fclose(gNavCacheLogFile);
1303 gNavCacheLogFile = NULL;
1304 gWriteLogMutex.unlock();