diff options
author | Cary Clark <cary@android.com> | 2010-09-29 16:37:54 -0400 |
---|---|---|
committer | Cary Clark <cary@android.com> | 2010-10-18 16:58:53 -0400 |
commit | a34b1107b2542fa334fd4db2d58e2df849e083b0 (patch) | |
tree | 1c85c319832b2e8314c127f022def0eecc02080e /WebKit/android/nav/CachedRoot.cpp | |
parent | e949efd529b667054f1e2abdda96995d6656b2b5 (diff) | |
download | external_webkit-a34b1107b2542fa334fd4db2d58e2df849e083b0.zip external_webkit-a34b1107b2542fa334fd4db2d58e2df849e083b0.tar.gz external_webkit-a34b1107b2542fa334fd4db2d58e2df849e083b0.tar.bz2 |
compute cursor rings when layers are transparent
Google search suggestions are drawn in a popup
menu (a div with a small amount of transparency).
This can partially or complete obscure links underneath the popup,
and can present touchable targets which may be much larger than
the text contained by the link.
CachedRoot::checkRings() determines if a larger bounding box
can be used for the ring around the link.
CachedRoot::maskIfHidden() determines if the ring needs to be
cut down in size because it is only partially visible, or fully
obscured.
Both routines share the implementation that gathers information
about the link, which uses RingCanvas to parse the picture, and
RingCheck to build layers describing the text and rectangles
drawn in the area around the ring.
The basic strategy is to find the text contained by the link
under consideration, and see if subsequent drawing obscures the
text, or if other text would be enclosed by enlarging the ring.
Since maskIfHidden() works better now than before, this CL
enabled checking for hidden links when recomputing the current
selection after the picture updates.
It also checks to see if the link can be larger when
maskIfHidden() determines that it is unclipped.
Also, if a tap is inside the larger ring, but not on the text
itself, treat that as a valid hit. (In CachedFrame::findBestHitAt)
And, this fixes CacheBuilder debugging code, and the CacheBuilder
array crasher described by bug: 3043268
bug: 2661613
Change-Id: I751f6539f6c840889a58de8c4611364442b3e37c
Diffstat (limited to 'WebKit/android/nav/CachedRoot.cpp')
-rw-r--r-- | WebKit/android/nav/CachedRoot.cpp | 609 |
1 files changed, 477 insertions, 132 deletions
diff --git a/WebKit/android/nav/CachedRoot.cpp b/WebKit/android/nav/CachedRoot.cpp index 15726ec..e86999c 100644 --- a/WebKit/android/nav/CachedRoot.cpp +++ b/WebKit/android/nav/CachedRoot.cpp @@ -38,6 +38,10 @@ #include "CachedRoot.h" +#if DEBUG_NAV_UI +#include "wtf/text/CString.h" +#endif + using std::min; using std::max; @@ -62,7 +66,10 @@ public: kDrawRect_Type, kDrawSprite_Type, kDrawText_Type, - kDrawTextOnPath_Type + kDrawTextOnPath_Type, + kPopLayer_Type, + kPushLayer_Type, + kPushSave_Type }; static bool isTextType(Type t) { @@ -110,7 +117,10 @@ public: "kDrawRect_Type", "kDrawSprite_Type", "kDrawText_Type", - "kDrawTextOnPath_Type" + "kDrawTextOnPath_Type", + "kPopLayer_Type", + "kPushLayer_Type", + "kPushSave_Type" }; #endif @@ -147,8 +157,9 @@ public: virtual bool onIRect(const SkIRect& rect) { if (joinGlyphs(rect)) return false; - bool interestingType = mType == kDrawBitmap_Type || - mType == kDrawRect_Type || isTextType(mType); + bool interestingType = mType == kDrawBitmap_Type + || mType == kDrawSprite_Type + || mType == kDrawRect_Type || isTextType(mType); if (SkIRect::Intersects(mBounds, rect) == false) { DBG_NAV_LOGD("BoundsCheck (no intersect) rect={%d,%d,%d,%d}" " mType=%s", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, @@ -255,9 +266,10 @@ public: } virtual void drawSprite(const SkBitmap& bitmap, int left, int top, - const SkPaint* paint = NULL) { + const SkPaint* paint) { mBounder.setType(CommonCheck::kDrawSprite_Type); - mBounder.setIsOpaque(bitmap.isOpaque()); + mBounder.setIsOpaque(bitmap.isOpaque() && + (!paint || paint->getAlpha() == 255)); SkCanvas::drawSprite(bitmap, left, top, paint); } @@ -283,8 +295,10 @@ public: mBounder.setEmpty(); mBounder.setType(CommonCheck::kDrawGlyph_Type); SkCanvas::drawPosTextH(text, byteLength, xpos, constY, paint); - if (mBounder.mUnion.isEmpty()) + if (mBounder.mUnion.isEmpty()) { + DBG_NAV_LOGD("empty constY=%g", SkScalarToFloat(constY)); return; + } SkPaint::FontMetrics metrics; paint.getFontMetrics(&metrics); SkPoint upDown[2] = { {xpos[0], constY + metrics.fAscent}, @@ -314,7 +328,7 @@ public: virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, SaveFlags flags) { - int depth = SkCanvas::saveLayer(bounds, paint, flags); + int depth = SkCanvas::save(flags); if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) { mTransparentLayer = depth; mBounder.setAllOpaque(false); @@ -323,6 +337,7 @@ public: } virtual void restore() { + mBounder.setType(CommonCheck::kDrawSprite_Type); // for layer draws int depth = getSaveCount(); if (depth == mTransparentLayer) { mTransparentLayer = 0; @@ -653,32 +668,404 @@ public: class RingCheck : public CommonCheck { public: RingCheck(const WTF::Vector<WebCore::IntRect>& rings, - const WebCore::IntPoint& location) : mSuccess(true) { + const WebCore::IntRect& bitBounds, const WebCore::IntRect& testBounds) + : mTestBounds(testBounds) + , mBitBounds(bitBounds) + { const WebCore::IntRect* r; for (r = rings.begin(); r != rings.end(); r++) { SkIRect fatter = {r->x(), r->y(), r->right(), r->bottom()}; fatter.inset(-CURSOR_RING_HIT_TEST_RADIUS, -CURSOR_RING_HIT_TEST_RADIUS); - DBG_NAV_LOGD("fat=(%d,%d,r=%d,b=%d)", fatter.fLeft, fatter.fTop, + DBG_NAV_LOGD("RingCheck fat=(%d,%d,r=%d,b=%d)", fatter.fLeft, fatter.fTop, fatter.fRight, fatter.fBottom); - mRings.op(fatter, SkRegion::kUnion_Op); + mTextSlop.op(fatter, SkRegion::kUnion_Op); + mTextTest.op(*r, SkRegion::kUnion_Op); } - DBG_NAV_LOGD("translate=(%d,%d)", -location.x(), -location.y()); - mRings.translate(-location.x(), -location.y()); + int dx = -bitBounds.x(); + int dy = -bitBounds.y(); + DBG_NAV_LOGD("RingCheck translate=(%d,%d)", dx, dy); + mTextSlop.translate(dx, dy); + mTextTest.translate(dx, dy); + mTestBounds.translate(dx, dy); + mEmpty.setEmpty(); } - virtual bool onIRect(const SkIRect& rect) { - if (mSuccess && mType == kDrawGlyph_Type) { - DBG_NAV_LOGD("contains (%d,%d,r=%d,b=%d) == %s", - rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, - mRings.contains(rect) ? "true" : "false"); - mSuccess &= mRings.contains(rect); + bool hiddenRings(SkRegion* clipped) + { + findBestLayer(); + if (!mBestLayer) { + DBG_NAV_LOG("RingCheck empty"); + clipped->setEmpty(); + return true; } + const SkRegion* layersEnd = mLayers.end(); + const Type* layerTypes = &mLayerTypes[mBestLayer - mLayers.begin()]; + bool collectGlyphs = true; + bool collectOvers = false; + SkRegion over; + for (const SkRegion* layers = mBestLayer; layers != layersEnd; layers++) { + Type layerType = *layerTypes++; + DBG_NAV_LOGD("RingCheck #%d %s (%d,%d,r=%d,b=%d)", + layers - mLayers.begin(), TypeNames[layerType], + layers->getBounds().fLeft, layers->getBounds().fTop, + layers->getBounds().fRight, layers->getBounds().fBottom); + if (collectGlyphs && layerType == kDrawGlyph_Type) { + DBG_NAV_LOGD("RingCheck #%d collectOvers", layers - mLayers.begin()); + collectOvers = true; + clipped->op(*layers, SkRegion::kUnion_Op); + } + collectGlyphs &= layerType != kPushLayer_Type; + if (collectOvers && (layerType == kDrawRect_Type + || (!collectGlyphs && layerType == kDrawSprite_Type))) + { + DBG_NAV_LOGD("RingCheck #%d over.op", layers - mLayers.begin()); + over.op(*layers, SkRegion::kUnion_Op); + } + } + bool result = !collectOvers || clipped->intersects(over); + const SkIRect t = clipped->getBounds(); + const SkIRect o = over.getBounds(); + clipped->op(over, SkRegion::kDifference_Op); + clipped->translate(mBitBounds.x(), mBitBounds.y()); + const SkIRect c = clipped->getBounds(); + DBG_NAV_LOGD("RingCheck intersects=%s text=(%d,%d,r=%d,b=%d)" + " over=(%d,%d,r=%d,b=%d) clipped=(%d,%d,r=%d,b=%d)", + result ? "true" : "false", + t.fLeft, t.fTop, t.fRight, t.fBottom, + o.fLeft, o.fTop, o.fRight, o.fBottom, + c.fLeft, c.fTop, c.fRight, c.fBottom); + return result; + } + + void push(Type type, const SkIRect& bounds) + { +#if DEBUG_NAV_UI + // this caches the push string and subquently ignores if pushSave + // is immediately followed by popLayer. Push/pop pairs happen + // frequently and just add noise to the log. + static String lastLog; + String currentLog = String("RingCheck append #") + + String::number(mLayers.size()) + + " type=" + TypeNames[type] + " bounds=(" + + String::number(bounds.fLeft) + + "," + String::number(bounds.fTop) + "," + + String::number(bounds.fRight) + "," + + String::number(bounds.fBottom) + ")"; + if (lastLog.length() == 0 || type != kPopLayer_Type) { + if (lastLog.length() != 0) + DBG_NAV_LOGD("%s", lastLog.latin1().data()); + if (type == kPushSave_Type) + lastLog = currentLog; + else + DBG_NAV_LOGD("%s", currentLog.latin1().data()); + } else + lastLog = ""; +#endif + popEmpty(); + if (type == kPopLayer_Type) { + Type last = mLayerTypes.last(); + // remove empty brackets + if (last == kPushLayer_Type || last == kPushSave_Type) { + mLayers.removeLast(); + mLayerTypes.removeLast(); + return; + } + // remove non-layer brackets + int stack = 0; + Type* types = mLayerTypes.end(); + while (types != mLayerTypes.begin()) { + Type type = *--types; + if (type == kPopLayer_Type) { + stack++; + continue; + } + if (type != kPushLayer_Type && type != kPushSave_Type) + continue; + if (--stack >= 0) + continue; + if (type == kPushLayer_Type) + break; + int remove = types - mLayerTypes.begin(); + DBG_NAV_LOGD("RingCheck remove=%d mLayers.size=%d" + " mLayerTypes.size=%d", remove, mLayers.size(), + mLayerTypes.size()); + mLayers.remove(remove); + mLayerTypes.remove(remove); + return; + } + } + mLayers.append(bounds); + mLayerTypes.append(type); + } + + void startText(const SkPaint& paint) + { + mPaint = &paint; + if (!mLayerTypes.isEmpty() && mLayerTypes.last() == kDrawGlyph_Type + && !mLayers.last().isEmpty()) { + push(kDrawGlyph_Type, mEmpty); + } + } + + bool textOutsideRings() + { + findBestLayer(); + if (!mBestLayer) { + DBG_NAV_LOG("RingCheck empty"); + return false; + } + const SkRegion* layers = mBestLayer; + const Type* layerTypes = &mLayerTypes[layers - mLayers.begin()]; + // back up to include text drawn before the best layer + SkRegion active = SkRegion(mBitBounds); + active.translate(-mBitBounds.x(), -mBitBounds.y()); + while (layers != mLayers.begin()) { + --layers; + Type layerType = *--layerTypes; + DBG_NAV_LOGD("RingCheck #%d %s" + " mTestBounds=(%d,%d,r=%d,b=%d) layers=(%d,%d,r=%d,b=%d)" + " active=(%d,%d,r=%d,b=%d)", + layers - mLayers.begin(), TypeNames[layerType], + mTestBounds.getBounds().fLeft, mTestBounds.getBounds().fTop, + mTestBounds.getBounds().fRight, mTestBounds.getBounds().fBottom, + layers->getBounds().fLeft, layers->getBounds().fTop, + layers->getBounds().fRight, layers->getBounds().fBottom, + active.getBounds().fLeft, active.getBounds().fTop, + active.getBounds().fRight, active.getBounds().fBottom); + if (layerType == kDrawRect_Type) { + SkRegion temp = *layers; + temp.op(mTestBounds, SkRegion::kIntersect_Op); + active.op(temp, SkRegion::kDifference_Op); + if (active.isEmpty()) { + DBG_NAV_LOGD("RingCheck #%d empty", layers - mLayers.begin()); + break; + } + } else if (layerType == kDrawGlyph_Type) { + SkRegion temp = *layers; + temp.op(active, SkRegion::kIntersect_Op); + if (!mTestBounds.intersects(temp)) + continue; + if (!mTestBounds.contains(temp)) + return false; + } else + break; + } + layers = mBestLayer; + layerTypes = &mLayerTypes[layers - mLayers.begin()]; + bool foundGlyph = false; + bool collectGlyphs = true; + do { + Type layerType = *layerTypes++; + DBG_NAV_LOGD("RingCheck #%d %s mTestBounds=(%d,%d,r=%d,b=%d)" + " layers=(%d,%d,r=%d,b=%d) collects=%s intersects=%s contains=%s", + layers - mLayers.begin(), TypeNames[layerType], + mTestBounds.getBounds().fLeft, mTestBounds.getBounds().fTop, + mTestBounds.getBounds().fRight, mTestBounds.getBounds().fBottom, + layers->getBounds().fLeft, layers->getBounds().fTop, + layers->getBounds().fRight, layers->getBounds().fBottom, + collectGlyphs ? "true" : "false", + mTestBounds.intersects(*layers) ? "true" : "false", + mTestBounds.contains(*layers) ? "true" : "false"); + if (collectGlyphs && layerType == kDrawGlyph_Type) { + if (!mTestBounds.intersects(*layers)) + continue; + if (!mTestBounds.contains(*layers)) + return false; + foundGlyph = true; + } + collectGlyphs &= layerType != kPushLayer_Type; + } while (++layers != mLayers.end()); + DBG_NAV_LOGD("RingCheck foundGlyph=%s", foundGlyph ? "true" : "false"); + return foundGlyph; + } + +protected: + virtual bool onIRect(const SkIRect& rect) + { + joinGlyphs(rect); + if (mType != kDrawGlyph_Type && mType != kDrawRect_Type + && mType != kDrawSprite_Type) + return false; + if (mLayerTypes.isEmpty() || mLayerTypes.last() != mType) + push(mType, mEmpty); + DBG_NAV_LOGD("RingCheck join %s (%d,%d,r=%d,b=%d) '%c'", + TypeNames[mType], rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + mCh); + mLayers.last().op(rect, SkRegion::kUnion_Op); return false; } - bool success() { return mSuccess; } - SkRegion mRings; - bool mSuccess; + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + mCh = ' '; + if (mPaint) { + SkUnichar unichar; + SkPaint utfPaint = *mPaint; + utfPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + utfPaint.glyphsToUnichars(&rec.fGlyphID, 1, &unichar); + mCh = unichar < 0x7f ? unichar : '?'; + } + return onIRect(rect); + } + +private: + int calcOverlap(SkRegion& testRegion) + { + if (testRegion.isEmpty()) + return INT_MAX; + testRegion.op(mTextTest, SkRegion::kXOR_Op); + SkRegion::Iterator iter(testRegion); + int area = 0; + while (!iter.done()) { + const SkIRect& cr = iter.rect(); + area += cr.width() * cr.height(); + iter.next(); + } + DBG_NAV_LOGD("RingCheck area=%d", area); + return area; + } + + void findBestLayer() + { + popEmpty(); + mBestLayer = 0; + const SkRegion* layers = mLayers.begin(); + const SkRegion* layersEnd = mLayers.end(); + if (layers == layersEnd) { + DBG_NAV_LOG("RingCheck empty"); + return; + } + // find text most like focus rings by xoring found with original + int bestArea = INT_MAX; + const SkRegion* testLayer = 0; + SkRegion testRegion; + const Type* layerTypes = &mLayerTypes[layers - mLayers.begin()]; + for (; layers != mLayers.end(); layers++) { + Type layerType = *layerTypes++; +#if DEBUG_NAV_UI + const SkIRect& gb = layers->getBounds(); + const SkIRect& tb = mTextSlop.getBounds(); + DBG_NAV_LOGD("RingCheck #%d %s mTextSlop=(%d,%d,%d,%d)" + " contains=%s bounds=(%d,%d,%d,%d)", + layers - mLayers.begin(), TypeNames[layerType], + tb.fLeft, tb.fTop, tb.fRight, tb.fBottom, + mTextSlop.contains(*layers) ? "true" : "false", + gb.fLeft, gb.fTop, gb.fRight, gb.fBottom); +#endif + if (layerType == kDrawGlyph_Type) { + if (!testLayer) + testLayer = layers; + if (mTextSlop.contains(*layers)) { + testRegion.op(*layers, SkRegion::kUnion_Op); + continue; + } + } + if (testLayer) { + int area = calcOverlap(testRegion); + if (bestArea > area) { + bestArea = area; + mBestLayer = testLayer; + } + DBG_NAV_LOGD("RingCheck #%d push test=%d best=%d", + layers - mLayers.begin(), testLayer - mLayers.begin(), + mBestLayer ? mBestLayer - mLayers.begin() : -1); + testRegion.setEmpty(); + testLayer = 0; + } + } + if (testLayer && bestArea > calcOverlap(testRegion)) { + DBG_NAV_LOGD("RingCheck last best=%d", testLayer - mLayers.begin()); + mBestLayer = testLayer; + } + } + + void popEmpty() + { + if (mLayerTypes.size() == 0) + return; + Type last = mLayerTypes.last(); + if (last >= kPopLayer_Type) + return; + const SkRegion& area = mLayers.last(); + if (!area.isEmpty()) + return; + DBG_NAV_LOGD("RingCheck #%d %s", mLayers.size() - 1, TypeNames[last]); + mLayers.removeLast(); + mLayerTypes.removeLast(); + } + + SkRegion mTestBounds; + IntRect mBitBounds; + SkIRect mEmpty; + const SkRegion* mBestLayer; + SkRegion mTextSlop; // outset rects for inclusion test + SkRegion mTextTest; // exact rects for xor area test + Type mLastType; + Vector<SkRegion> mLayers; + Vector<Type> mLayerTypes; + const SkPaint* mPaint; + char mCh; +}; + +class RingCanvas : public BoundsCanvas { +public: + RingCanvas(RingCheck* bounder) + : INHERITED(bounder) + { + } + +protected: + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawText(text, byteLength, x, y, paint); + } + + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawPosText(text, byteLength, pos, paint); + } + + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawTextOnPath(text, byteLength, path, matrix, paint); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + static_cast<RingCheck&>(mBounder).startText(paint); + INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); + } + + virtual int save(SaveFlags flags) + { + RingCheck& bounder = static_cast<RingCheck&>(mBounder); + bounder.push(CommonCheck::kPushSave_Type, getTotalClip().getBounds()); + return INHERITED::save(flags); + } + + virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, + SaveFlags flags) + { + RingCheck& bounder = static_cast<RingCheck&>(mBounder); + bounder.push(CommonCheck::kPushLayer_Type, getTotalClip().getBounds()); + return INHERITED::save(flags); + } + + virtual void restore() + { + RingCheck& bounder = static_cast<RingCheck&>(mBounder); + bounder.push(CommonCheck::kPopLayer_Type, getTotalClip().getBounds()); + INHERITED::restore(); + } + +private: + typedef BoundsCanvas INHERITED; }; bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction direction, @@ -709,6 +1096,29 @@ bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction directio return false; } +void CachedRoot::calcBitBounds(const IntRect& nodeBounds, IntRect* bitBounds) const +{ + IntRect contentBounds = IntRect(0, 0, mPicture->width(), mPicture->height()); + IntRect overBounds = nodeBounds; + overBounds.inflate(kMargin); + *bitBounds = mScrolledBounds; + bitBounds->unite(mViewBounds); + bitBounds->intersect(contentBounds); + bitBounds->intersect(overBounds); + DBG_NAV_LOGD("contentBounds=(%d,%d,r=%d,b=%d) overBounds=(%d,%d,r=%d,b=%d)" + " mScrolledBounds=(%d,%d,r=%d,b=%d) mViewBounds=(%d,%d,r=%d,b=%d)" + " bitBounds=(%d,%d,r=%d,b=%d)", + contentBounds.x(), contentBounds.y(), contentBounds.right(), + contentBounds.bottom(), + overBounds.x(), overBounds.y(), overBounds.right(), overBounds.bottom(), + mScrolledBounds.x(), mScrolledBounds.y(), mScrolledBounds.right(), + mScrolledBounds.bottom(), + mViewBounds.x(), mViewBounds.y(), mViewBounds.right(), + mViewBounds.bottom(), + bitBounds->x(), bitBounds->y(), bitBounds->right(), + bitBounds->bottom()); +} + int CachedRoot::checkForCenter(int x, int y) const { @@ -745,22 +1155,30 @@ void CachedRoot::checkForJiggle(int* xDeltaPtr) const bool CachedRoot::checkRings(SkPicture* picture, const WTF::Vector<WebCore::IntRect>& rings, - const WebCore::IntRect& bounds) const + const WebCore::IntRect& nodeBounds, + const WebCore::IntRect& testBounds) const { if (!picture) return false; - RingCheck ringCheck(rings, bounds.location()); - BoundsCanvas checker(&ringCheck); + IntRect bitBounds; + calcBitBounds(nodeBounds, &bitBounds); + RingCheck ringCheck(rings, bitBounds, testBounds); + RingCanvas checker(&ringCheck); SkBitmap bitmap; - bitmap.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(), - bounds.height()); + bitmap.setConfig(SkBitmap::kARGB_8888_Config, bitBounds.width(), + bitBounds.height()); checker.setBitmapDevice(bitmap); - checker.translate(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y())); + checker.translate(SkIntToScalar(-bitBounds.x()), + SkIntToScalar(-bitBounds.y())); checker.drawPicture(*picture); - DBG_NAV_LOGD("bounds=(%d,%d,r=%d,b=%d) success=%s", - bounds.x(), bounds.y(), bounds.right(), bounds.bottom(), - ringCheck.success() ? "true" : "false"); - return ringCheck.success(); + bool result = ringCheck.textOutsideRings(); + DBG_NAV_LOGD("bitBounds=(%d,%d,r=%d,b=%d) nodeBounds=(%d,%d,r=%d,b=%d)" + " testBounds=(%d,%d,r=%d,b=%d) success=%s", + bitBounds.x(), bitBounds.y(), bitBounds.right(), bitBounds.bottom(), + nodeBounds.x(), nodeBounds.y(), nodeBounds.right(), nodeBounds.bottom(), + testBounds.x(), testBounds.y(), testBounds.right(), testBounds.bottom(), + result ? "true" : "false"); + return result; } void CachedRoot::draw(FindCanvas& canvas) const @@ -1085,7 +1503,7 @@ WTF::String CachedRoot::imageURI(int x, int y) const bool CachedRoot::maskIfHidden(BestData* best) const { const CachedNode* bestNode = best->mNode; - if (bestNode->isUnclipped() || bestNode->isTransparent()) + if (bestNode->isUnclipped()) return false; const CachedFrame* frame = best->mFrame; SkPicture* picture = frame->picture(bestNode); @@ -1093,115 +1511,42 @@ bool CachedRoot::maskIfHidden(BestData* best) const DBG_NAV_LOG("missing picture"); return false; } - // given the picture matching this nav cache - // create an SkBitmap with dimensions of the cursor intersected w/ extended view - const WebCore::IntRect& nodeBounds = bestNode->bounds(frame); - WebCore::IntRect bounds = nodeBounds; - bounds.intersect(mScrolledBounds); - int leftMargin = bounds.x() == nodeBounds.x() ? kMargin : 0; - int topMargin = bounds.y() == nodeBounds.y() ? kMargin : 0; - int rightMargin = bounds.right() == nodeBounds.right() ? kMargin : 0; - int bottomMargin = bounds.bottom() == nodeBounds.bottom() ? kMargin : 0; - bool unclipped = (leftMargin & topMargin & rightMargin & bottomMargin) != 0; - WebCore::IntRect marginBounds = nodeBounds; - marginBounds.inflate(kMargin); - marginBounds.intersect(mScrolledBounds); - SkScalar offsetX = SkIntToScalar(leftMargin - bounds.x()); - SkScalar offsetY = SkIntToScalar(topMargin - bounds.y()); -#if USE(ACCELERATED_COMPOSITING) - // When cached nodes are constructed in CacheBuilder.cpp, their - // unclipped attribute is set so this condition won't be reached. - // In the future, layers may contain nodes that can be clipped. - // So to be safe, adjust the layer picture by its offset. - if (bestNode->isInLayer()) { - const CachedLayer* cachedLayer = frame->layer(bestNode); - const LayerAndroid* layer = cachedLayer->layer(mRootLayer); - SkMatrix pictMatrix; - layer->localToGlobal(&pictMatrix); - // FIXME: ignore scale, rotation for now - offsetX += pictMatrix.getTranslateX(); - offsetY += pictMatrix.getTranslateY(); - DBG_NAV_LOGD("layer picture=%p (%g,%g)", picture, - pictMatrix.getTranslateX(), pictMatrix.getTranslateY()); - } -#endif - BoundsCheck boundsCheck; - BoundsCanvas checker(&boundsCheck); - boundsCheck.mBounds.set(leftMargin, topMargin, - leftMargin + bounds.width(), topMargin + bounds.height()); - boundsCheck.mBoundsSlop = boundsCheck.mBounds; - boundsCheck.mBoundsSlop.inset(-kSlop, -kSlop); + Vector<IntRect> rings; + bestNode->cursorRings(frame, &rings); + const WebCore::IntRect& bounds = bestNode->bounds(frame); + IntRect bitBounds; + calcBitBounds(bounds, &bitBounds); + RingCheck ringCheck(rings, bitBounds, bounds); + RingCanvas checker(&ringCheck); SkBitmap bitmap; - bitmap.setConfig(SkBitmap::kARGB_8888_Config, marginBounds.width(), - marginBounds.height()); + bitmap.setConfig(SkBitmap::kARGB_8888_Config, bitBounds.width(), + bitBounds.height()); checker.setBitmapDevice(bitmap); - // insert probes to be called when the data corresponding to this ring is drawn - // need to know if ring was generated by text, image, or parent (like div) - // ? need to know (like imdb menu bar) to give up sometimes (when?) - checker.translate(offsetX, offsetY); + checker.translate(SkIntToScalar(-bitBounds.x()), + SkIntToScalar(-bitBounds.y())); checker.drawPicture(*picture); - boundsCheck.checkLast(); - // was it not drawn or clipped out? + SkRegion clipRgn; + bool clipped = ringCheck.hiddenRings(&clipRgn); CachedNode* node = const_cast<CachedNode*>(best->mNode); - if (boundsCheck.hidden()) { // if hidden, return false so that nav can try again -#if DEBUG_NAV_UI - const SkIRect& m = boundsCheck.mBounds; - const SkIRect& s = boundsCheck.mBoundsSlop; - DBG_NAV_LOGD("hidden node:%p (%d) mBounds={%d,%d,%d,%d} mBoundsSlop=" - "{%d,%d,%d,%d}", node, node->index(), - m.fLeft, m.fTop, m.fRight, m.fBottom, - s.fLeft, s.fTop, s.fRight, s.fBottom); - const SkIRect& o = boundsCheck.mDrawnOver.getBounds(); - const SkIRect& l = boundsCheck.mLastAll; - const SkIRect& u = boundsCheck.mUnion; - DBG_NAV_LOGD("hidden mDrawnOver={%d,%d,%d,%d} mLastAll={%d,%d,%d,%d}" - " mUnion={%d,%d,%d,%d}", - o.fLeft, o.fTop, o.fRight, o.fBottom, - l.fLeft, l.fTop, l.fRight, l.fBottom, - u.fLeft, u.fTop, u.fRight, u.fBottom); - const SkIRect& a = boundsCheck.mAllDrawnIn; - const WebCore::IntRect& c = mScrolledBounds; - const WebCore::IntRect& b = nodeBounds; - DBG_NAV_LOGD("hidden mAllDrawnIn={%d,%d,%d,%d}" - " mScrolledBounds={%d,%d,%d,%d} nodeBounds={%d,%d,%d,%d}", - a.fLeft, a.fTop, a.fRight, a.fBottom, - c.x(), c.y(), c.right(), c.bottom(), - b.x(), b.y(), b.right(), b.bottom()); - DBG_NAV_LOGD("bits.mWidth=%d bits.mHeight=%d transX=%d transY=%d", - marginBounds.width(),marginBounds.height(), - kMargin - bounds.x(), kMargin - bounds.y()); -#endif + DBG_NAV_LOGD("clipped=%s clipRgn.isEmpty=%s", clipped ? "true" : "false", + clipRgn.isEmpty() ? "true" : "false"); + if (clipped && clipRgn.isEmpty()) { node->setDisabled(true); - node->setClippedOut(unclipped == false); + IntRect clippedBounds = bounds; + clippedBounds.intersect(bitBounds); + node->setClippedOut(clippedBounds != bounds); return true; } // was it partially occluded by later drawing? // if partially occluded, modify the bounds so that the mouse click has a better x,y - const SkIRect& over = boundsCheck.mDrawnOver.getBounds(); - if (over.isEmpty() == false) { -#if DEBUG_NAV_UI - SkIRect orig = boundsCheck.mBounds; -#endif - SkIRect& base = boundsCheck.mBounds; - if (base.fLeft < over.fRight && base.fRight > over.fRight) - base.fLeft = over.fRight; - else if (base.fRight > over.fLeft && base.fLeft < over.fLeft) - base.fRight = over.fLeft; - if (base.fTop < over.fBottom && base.fBottom > over.fBottom) - base.fTop = over.fBottom; - else if (base.fBottom > over.fTop && base.fTop < over.fTop) - base.fBottom = over.fTop; -#if DEBUG_NAV_UI - const SkIRect& modded = boundsCheck.mBounds; - DBG_NAV_LOGD("partially occluded node:%p (%d) old:{%d,%d,%d,%d}" - " new:{%d,%d,%d,%d}", node, node->index(), - orig.fLeft, orig.fTop, orig.fRight, orig.fBottom, - base.fLeft, base.fTop, base.fRight, base.fBottom); -#endif - best->setMouseBounds(WebCore::IntRect(bounds.x() + base.fLeft - kMargin, - bounds.y() + base.fTop - kMargin, base.width(), base.height())); + if (clipped) { + DBG_NAV_LOGD("clipped clipRgn={%d,%d,r=%d,b=%d}", + clipRgn.getBounds().fLeft, clipRgn.getBounds().fTop, + clipRgn.getBounds().fRight, clipRgn.getBounds().fBottom); + best->setMouseBounds(clipRgn.getBounds()); node->clip(best->mouseBounds()); - } + } else + node->fixUpCursorRects(frame); return false; } |