| /* |
| * Copyright 2007, The Android Open Source Project |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "CachedPrefix.h" |
| #include "CachedHistory.h" |
| #include "CachedNode.h" |
| #include "CachedRoot.h" |
| |
| #include "CachedFrame.h" |
| |
| #define OFFSETOF(type, field) ((char*)&(((type*)1)->field) - (char*)1) // avoids gnu warning |
| |
| #define MIN_OVERLAP 3 // if rects overlap by 2 pixels or fewer, treat them as non-intersecting |
| |
| namespace android { |
| |
| WebCore::IntRect CachedFrame::adjustBounds(const CachedNode* node, |
| const WebCore::IntRect& rect) const |
| { |
| DBG_NAV_LOGD("node=%p [%d] rect=(%d,%d,w=%d,h=%d)", |
| node, node->index(), rect.x(), rect.y(), rect.width(), rect.height()); |
| #if USE(ACCELERATED_COMPOSITING) |
| return layer(node)->adjustBounds(mRoot->rootLayer(), rect); |
| #else |
| return rect; |
| #endif |
| } |
| |
| bool CachedFrame::CheckBetween(Direction direction, const WebCore::IntRect& bestRect, |
| const WebCore::IntRect& prior, WebCore::IntRect* result) |
| { |
| int left, top, width, height; |
| if (direction & UP_DOWN) { |
| top = direction == UP ? bestRect.bottom() : prior.bottom(); |
| int bottom = direction == UP ? prior.y() : bestRect.y(); |
| height = bottom - top; |
| if (height < 0) |
| return false; |
| left = prior.x(); |
| int testLeft = bestRect.x(); |
| if (left > testLeft) |
| left = testLeft; |
| int right = prior.right(); |
| int testRight = bestRect.right(); |
| if (right < testRight) |
| right = testRight; |
| width = right - left; |
| } else { |
| left = direction == LEFT ? bestRect.right() : prior.right(); |
| int right = direction == LEFT ? prior.x() : bestRect.x(); |
| width = right - left; |
| if (width < 0) |
| return false; |
| top = prior.y(); |
| int testTop = bestRect.y(); |
| if (top > testTop) |
| top = testTop; |
| int bottom = prior.bottom(); |
| int testBottom = bestRect.bottom(); |
| if (bottom < testBottom) |
| bottom = testBottom; |
| height = bottom - top; |
| } |
| *result = WebCore::IntRect(left, top, width, height); |
| return true; |
| } |
| |
| bool CachedFrame::checkBetween(BestData* best, Direction direction) |
| { |
| const WebCore::IntRect& bestRect = best->bounds(); |
| BestData test; |
| test.mDistance = INT_MAX; |
| test.mNode = NULL; |
| int index = direction; |
| int limit = index + DIRECTION_COUNT; |
| do { |
| WebCore::IntRect edges; |
| Direction check = (Direction) (index & DIRECTION_MASK); |
| if (CheckBetween(check, bestRect, |
| history()->priorBounds(), &edges) == false) |
| continue; |
| WebCore::IntRect clip = mRoot->scrolledBounds(); |
| clip.intersect(edges); |
| if (clip.isEmpty()) |
| continue; |
| findClosest(&test, direction, check, &clip); |
| if (test.mNode == NULL) |
| continue; |
| if (direction == check) |
| break; |
| } while (++index < limit); |
| if (test.mNode == NULL) |
| return false; |
| *best = test; |
| return true; |
| } |
| |
| bool CachedFrame::checkRings(const CachedNode* node, |
| const WTF::Vector<WebCore::IntRect>& rings, |
| const WebCore::IntRect& bounds) const |
| { |
| return mRoot->checkRings(picture(node), rings, bounds); |
| } |
| |
| bool CachedFrame::checkVisited(const CachedNode* node, Direction direction) const |
| { |
| return history()->checkVisited(node, direction); |
| } |
| |
| void CachedFrame::clearCursor() |
| { |
| DBG_NAV_LOGD("mCursorIndex=%d", mCursorIndex); |
| if (mCursorIndex < CURSOR_SET) |
| return; |
| CachedNode& cursor = mCachedNodes[mCursorIndex]; |
| cursor.clearCursor(this); |
| mCursorIndex = CURSOR_CLEARED; // initialized and explicitly cleared |
| } |
| |
| // returns 0 if test is preferable to best, 1 if not preferable, or -1 if unknown |
| int CachedFrame::compare(BestData& testData, const BestData& bestData) const |
| { |
| if (testData.mNode->tabIndex() != bestData.mNode->tabIndex()) { |
| if (testData.mNode->tabIndex() < bestData.mNode->tabIndex() |
| || (mRoot->mCursor && mRoot->mCursor->tabIndex() < bestData.mNode->tabIndex())) { |
| testData.mNode->setCondition(CachedNode::HIGHER_TAB_INDEX); |
| return REJECT_TEST; |
| } |
| return TEST_IS_BEST; |
| } |
| // if the test minor axis line intersects the line segment between cursor |
| // center and best center, choose it |
| // give more weight to exact major axis alignment (rows, columns) |
| if (testData.mInNav != bestData.mInNav) { |
| if (bestData.mInNav) { |
| testData.mNode->setCondition(CachedNode::IN_CURSOR); |
| return REJECT_TEST; |
| } |
| return TEST_IS_BEST; |
| } |
| if (testData.mInNav) { |
| if (bestData.mMajorDelta < testData.mMajorDelta) { |
| testData.mNode->setCondition(CachedNode::CLOSER_IN_CURSOR); |
| return REJECT_TEST; |
| } |
| if (testData.mMajorDelta < bestData.mMajorDelta) |
| return TEST_IS_BEST; |
| } |
| if (testData.mMajorDelta < 0 && bestData.mMajorDelta >= 0) { |
| testData.mNode->setCondition(CachedNode::FURTHER); |
| return REJECT_TEST; |
| } |
| if ((testData.mMajorDelta ^ bestData.mMajorDelta) < 0) // one above, one below (or one left, one right) |
| return TEST_IS_BEST; |
| bool bestInWorking = bestData.inOrSubsumesWorking(); |
| bool testInWorking = testData.inOrSubsumesWorking(); |
| if (bestInWorking && testData.mWorkingOutside && testData.mNavOutside) { |
| testData.mNode->setCondition(CachedNode::IN_WORKING); |
| return REJECT_TEST; |
| } |
| if (testInWorking && bestData.mWorkingOutside && bestData.mNavOutside) |
| return TEST_IS_BEST; |
| bool bestInNav = directionChange() && bestData.inOrSubsumesNav(); |
| bool testInNav = directionChange() && testData.inOrSubsumesNav(); |
| if (bestInWorking == false && testInWorking == false) { |
| if (bestInNav && testData.mNavOutside) { |
| testData.mNode->setCondition(CachedNode::IN_UMBRA); |
| return REJECT_TEST; |
| } |
| if (testInNav && bestData.mNavOutside) |
| return TEST_IS_BEST; |
| } |
| #if 01 // hopefully butt test will remove need for this |
| if (testData.mCursorChild != bestData.mCursorChild) { |
| if (bestData.mCursorChild) { |
| testData.mNode->setCondition(CachedNode::IN_CURSOR_CHILDREN); |
| return REJECT_TEST; |
| } |
| return TEST_IS_BEST; |
| } |
| #endif |
| bool bestTestIn = (bestInWorking || bestInNav) && (testInWorking || testInNav); |
| bool testOverlap = bestTestIn || (testData.mWorkingOverlap != 0 && bestData.mWorkingOverlap == 0); |
| bool bestOverlap = bestTestIn || (testData.mWorkingOverlap == 0 && bestData.mWorkingOverlap != 0); |
| #if 01 // this isn't working? |
| if (testOverlap == bestOverlap) { |
| if (bestData.mMajorButt < 10 && testData.mMajorButt >= 40) { |
| testData.mNode->setCondition(CachedNode::BUTTED_UP); |
| return REJECT_TEST; |
| } |
| if (testData.mMajorButt < 10 && bestData.mMajorButt >= 40) |
| return TEST_IS_BEST; |
| } |
| #endif |
| if (bestOverlap && bestData.mMajorDelta < testData.mMajorDelta) { // choose closest major axis center |
| testData.mNode->setCondition(CachedNode::CLOSER); |
| return REJECT_TEST; |
| } |
| if (testOverlap && testData.mMajorDelta < bestData.mMajorDelta) |
| return TEST_IS_BEST; |
| if (bestOverlap && bestData.mMajorDelta2 < testData.mMajorDelta2) { |
| testData.mNode->setCondition(CachedNode::CLOSER_TOP); |
| return REJECT_TEST; |
| } |
| if (testOverlap && testData.mMajorDelta2 < bestData.mMajorDelta2) |
| return TEST_IS_BEST; |
| #if 01 |
| if (bestOverlap && ((bestData.mSideDistance <= 0 && testData.mSideDistance > 0) || |
| abs(bestData.mSideDistance) < abs(testData.mSideDistance))) { |
| testData.mNode->setCondition(CachedNode::LEFTMOST); |
| return REJECT_TEST; |
| } |
| if (testOverlap && ((testData.mSideDistance <= 0 && bestData.mSideDistance > 0) || |
| abs(testData.mSideDistance) < abs(bestData.mSideDistance))) |
| return TEST_IS_BEST; |
| // fix me : the following ASSERT fires -- not sure if this case should be handled or not |
| // ASSERT(bestOverlap == false && testOverlap == false); |
| #endif |
| SkFixed testMultiplier = testData.mWorkingOverlap > testData.mNavOverlap ? |
| testData.mWorkingOverlap : testData.mNavOverlap; |
| SkFixed bestMultiplier = bestData.mWorkingOverlap > bestData.mNavOverlap ? |
| bestData.mWorkingOverlap : bestData.mNavOverlap; |
| int testDistance = testData.mDistance; |
| int bestDistance = bestData.mDistance; |
| // start here; |
| // this fails if they're off by 1 |
| // try once again to implement sliding scale so that off by 1 is nearly like zero, |
| // and off by a lot causes sideDistance to have little or no effect |
| // try elliptical distance -- lengthen side contribution |
| // these ASSERTs should not fire, but do fire on mail.google.com |
| // can't debug yet, won't reproduce |
| ASSERT(testDistance >= 0); |
| ASSERT(bestDistance >= 0); |
| testDistance += testDistance; // multiply by 2 |
| testDistance *= testDistance; |
| bestDistance += bestDistance; // multiply by 2 |
| bestDistance *= bestDistance; |
| int side = testData.mSideDistance; |
| int negative = side < 0 && bestData.mSideDistance > 0; |
| side *= side; |
| if (negative) |
| side = -side; |
| testDistance += side; |
| side = bestData.mSideDistance; |
| negative = side < 0 && testData.mSideDistance > 0; |
| side *= side; |
| if (negative) |
| side = -side; |
| bestDistance += side; |
| if (testMultiplier > (SK_Fixed1 >> 1) || bestMultiplier > (SK_Fixed1 >> 1)) { // considerable working overlap? |
| testDistance = SkFixedMul(testDistance, bestMultiplier); |
| bestDistance = SkFixedMul(bestDistance, testMultiplier); |
| } |
| if (bestDistance < testDistance) { |
| testData.mNode->setCondition(CachedNode::CLOSER_OVERLAP); |
| return REJECT_TEST; |
| } |
| if (testDistance < bestDistance) |
| return TEST_IS_BEST; |
| #if 0 |
| int distance = testData.mDistance + testData.mSideDistance; |
| int best = bestData.mDistance + bestData.mSideDistance; |
| if (distance > best) { |
| testData.mNode->setCondition(CachedNode::CLOSER_RAW_DISTANCE); |
| return REJECT_TEST; |
| } |
| else if (distance < best) |
| return TEST_IS_BEST; |
| best = bestData.mSideDistance; |
| if (testData.mSideDistance > best) { |
| testData.mNode->setCondition(CachedNode::SIDE_DISTANCE); |
| return REJECT_TEST; |
| } |
| if (testData.mSideDistance < best) |
| return TEST_IS_BEST; |
| #endif |
| if (testData.mPreferred < bestData.mPreferred) { |
| testData.mNode->setCondition(CachedNode::PREFERRED); |
| return REJECT_TEST; |
| } |
| if (testData.mPreferred > bestData.mPreferred) |
| return TEST_IS_BEST; |
| return UNDECIDED; |
| } |
| |
| const CachedNode* CachedFrame::currentCursor(const CachedFrame** framePtr) const |
| { |
| if (framePtr) |
| *framePtr = this; |
| if (mCursorIndex < CURSOR_SET) |
| return NULL; |
| const CachedNode* result = &mCachedNodes[mCursorIndex]; |
| const CachedFrame* frame = hasFrame(result); |
| if (frame != NULL) |
| return frame->currentCursor(framePtr); |
| (const_cast<CachedNode*>(result))->fixUpCursorRects(this); |
| return result; |
| } |
| |
| const CachedNode* CachedFrame::currentFocus(const CachedFrame** framePtr) const |
| { |
| if (framePtr) |
| *framePtr = this; |
| if (mFocusIndex < 0) |
| return NULL; |
| const CachedNode* result = &mCachedNodes[mFocusIndex]; |
| const CachedFrame* frame = hasFrame(result); |
| if (frame != NULL) |
| return frame->currentFocus(framePtr); |
| return result; |
| } |
| |
| bool CachedFrame::directionChange() const |
| { |
| return history()->directionChange(); |
| } |
| |
| #ifdef BROWSER_DEBUG |
| CachedNode* CachedFrame::find(WebCore::Node* node) // !!! probably debugging only |
| { |
| for (CachedNode* test = mCachedNodes.begin(); test != mCachedNodes.end(); test++) |
| if (node == test->webCoreNode()) |
| return test; |
| for (CachedFrame* frame = mCachedFrames.begin(); frame != mCachedFrames.end(); |
| frame++) { |
| CachedNode* result = frame->find(node); |
| if (result != NULL) |
| return result; |
| } |
| return NULL; |
| } |
| #endif |
| |
| const CachedNode* CachedFrame::findBestAt(const WebCore::IntRect& rect, |
| int* best, bool* inside, const CachedNode** directHit, |
| const CachedFrame** framePtr, int* x, int* y, |
| bool checkForHiddenStart) const |
| { |
| const CachedNode* result = NULL; |
| int rectWidth = rect.width(); |
| WebCore::IntPoint center = WebCore::IntPoint(rect.x() + (rectWidth >> 1), |
| rect.y() + (rect.height() >> 1)); |
| mRoot->setupScrolledBounds(); |
| for (const CachedNode* test = mCachedNodes.begin(); test != mCachedNodes.end(); test++) { |
| if (test->disabled()) |
| continue; |
| size_t parts = test->navableRects(); |
| BestData testData; |
| testData.mNode = test; |
| testData.mFrame = this; |
| WebCore::IntRect bounds = test->bounds(this); |
| testData.setMouseBounds(bounds); |
| testData.setNodeBounds(bounds); |
| bool checkForHidden = checkForHiddenStart; |
| for (size_t part = 0; part < parts; part++) { |
| WebCore::IntRect testRect = test->ring(this, part); |
| if (test->isInLayer()) { |
| DBG_NAV_LOGD("[%d] intersects=%ss testRect=(%d,%d,w=%d,h=%d)" |
| " rect=(%d,%d,w=%d,h=%d)", test->index(), |
| testRect.intersects(rect) ? "true" : "false", |
| testRect.x(), testRect.y(), testRect.width(), testRect.height(), |
| rect.x(), rect.y(), rect.width(), rect.height()); |
| } |
| if (testRect.intersects(rect)) { |
| if (checkForHidden && mRoot->maskIfHidden(&testData) == true) |
| break; |
| checkForHidden = false; |
| testRect.intersect(testData.mouseBounds()); |
| if (testRect.contains(center)) { |
| // We have a direct hit. |
| if (*directHit == NULL) { |
| *directHit = test; |
| *framePtr = this; |
| *x = center.x(); |
| *y = center.y(); |
| } else { |
| // We have hit another one before |
| const CachedNode* d = *directHit; |
| if (d->bounds(this).contains(testRect)) { |
| // This rectangle is inside the other one, so it is |
| // the best one. |
| *directHit = test; |
| *framePtr = this; |
| } |
| } |
| } |
| if (NULL != *directHit) { |
| // If we have a direct hit already, there is no need to |
| // calculate the distances, or check the other parts |
| break; |
| } |
| WebCore::IntRect both = rect; |
| int smaller = testRect.width() < testRect.height() ? |
| testRect.width() : testRect.height(); |
| smaller -= rectWidth; |
| int inset = smaller < rectWidth ? smaller : rectWidth; |
| inset >>= 1; // inflate doubles the width decrease |
| if (inset > 1) |
| both.inflate(1 - inset); |
| both.intersect(testRect); |
| if (both.isEmpty()) |
| continue; |
| bool testInside = testRect.contains(center); |
| if (*inside && !testInside) |
| continue; |
| WebCore::IntPoint testCenter = WebCore::IntPoint(testRect.x() + |
| (testRect.width() >> 1), testRect.y() + (testRect.height() >> 1)); |
| int dx = testCenter.x() - center.x(); |
| int dy = testCenter.y() - center.y(); |
| int distance = dx * dx + dy * dy; |
| if ((!*inside && testInside) || *best >= distance) { |
| *best = distance; |
| *inside = testInside; |
| result = test; |
| *framePtr = this; |
| *x = both.x() + (both.width() >> 1); |
| *y = both.y() + (both.height() >> 1); |
| } |
| } |
| } |
| } |
| for (const CachedFrame* frame = mCachedFrames.begin(); |
| frame != mCachedFrames.end(); frame++) { |
| const CachedNode* frameResult = frame->findBestAt(rect, best, inside, |
| directHit, framePtr, x, y, checkForHiddenStart); |
| if (NULL != frameResult) |
| result = frameResult; |
| } |
| if (NULL != *directHit) { |
| result = *directHit; |
| } |
| return result; |
| } |
| |
| const CachedFrame* CachedFrame::findBestFrameAt(int x, int y) const |
| { |
| if (mLocalViewBounds.contains(x, y) == false) |
| return NULL; |
| const CachedFrame* result = this; |
| for (const CachedFrame* frame = mCachedFrames.begin(); |
| frame != mCachedFrames.end(); frame++) { |
| const CachedFrame* frameResult = frame->findBestFrameAt(x, y); |
| if (NULL != frameResult) |
| result = frameResult; |
| } |
| return result; |
| } |
| |
| const CachedNode* CachedFrame::findBestHitAt(const WebCore::IntRect& rect, |
| const CachedFrame** framePtr, int* x, int* y) const |
| { |
| mRoot->setupScrolledBounds(); |
| for (const CachedFrame* frame = mCachedFrames.end() - 1; |
| frame != mCachedFrames.begin() - 1; frame--) { |
| const CachedNode* frameResult = frame->findBestHitAt(rect, |
| framePtr, x, y); |
| if (NULL != frameResult) |
| return frameResult; |
| } |
| for (const CachedNode* test = mCachedNodes.end() - 1; |
| test != mCachedNodes.begin() - 1; test--) { |
| if (test->disabled()) |
| continue; |
| WebCore::IntRect testRect = test->hitBounds(this); |
| if (testRect.intersects(rect) == false) |
| continue; |
| BestData testData; |
| testData.mNode = test; |
| testData.mFrame = this; |
| testData.setMouseBounds(testRect); |
| testData.setNodeBounds(testRect); |
| if (mRoot->maskIfHidden(&testData) == true) |
| continue; |
| for (int i = 0; i < test->navableRects(); i++) { |
| WebCore::IntRect cursorRect = test->ring(this, i); |
| if (cursorRect.intersects(rect)) { |
| WebCore::IntRect intersection(cursorRect); |
| intersection.intersect(rect); |
| *x = intersection.x() + (intersection.width() >> 1); |
| *y = intersection.y() + (intersection.height() >> 1); |
| *framePtr = this; |
| return test; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| void CachedFrame::findClosest(BestData* bestData, Direction originalDirection, |
| Direction direction, WebCore::IntRect* clip) const |
| { |
| const CachedNode* test = mCachedNodes.begin(); |
| while ((test = test->traverseNextNode()) != NULL) { |
| const CachedFrame* child = hasFrame(test); |
| if (child != NULL) { |
| const CachedNode* childDoc = child->validDocument(); |
| if (childDoc == NULL) |
| continue; |
| child->findClosest(bestData, originalDirection, direction, clip); |
| } |
| if (test->noSecondChance()) |
| continue; |
| if (test->isNavable(this, *clip) == false) |
| continue; |
| if (checkVisited(test, originalDirection) == false) |
| continue; |
| size_t partMax = test->navableRects(); |
| for (size_t part = 0; part < partMax; part++) { |
| WebCore::IntRect testBounds = test->ring(this, part); |
| if (clip->intersects(testBounds) == false) |
| continue; |
| if (clip->contains(testBounds) == false) { |
| if (direction & UP_DOWN) { |
| // if (testBounds.x() > clip->x() || testBounds.right() < clip->right()) |
| // continue; |
| testBounds.setX(clip->x()); |
| testBounds.setWidth(clip->width()); |
| } else { |
| // if (testBounds.y() > clip->y() || testBounds.bottom() < clip->bottom()) |
| // continue; |
| testBounds.setY(clip->y()); |
| testBounds.setHeight(clip->height()); |
| } |
| if (clip->contains(testBounds) == false) |
| continue; |
| } |
| int distance; |
| // seems like distance for UP for instance needs to be 'test top closest to |
| // clip bottom' -- keep the old code but try this instead |
| switch (direction) { |
| #if 0 |
| case LEFT: distance = testBounds.x() - clip->x(); break; |
| case RIGHT: distance = clip->right() - testBounds.right(); break; |
| case UP: distance = testBounds.y() - clip->y(); break; |
| case DOWN: distance = clip->bottom() - testBounds.bottom(); break; |
| #else |
| case LEFT: distance = clip->right() - testBounds.x(); break; |
| case RIGHT: distance = testBounds.right() - clip->x(); break; |
| case UP: distance = clip->bottom() - testBounds.y(); break; |
| case DOWN: distance = testBounds.bottom() - clip->y(); break; |
| #endif |
| default: |
| distance = 0; ASSERT(0); |
| } |
| if (distance < bestData->mDistance) { |
| bestData->mNode = test; |
| bestData->mFrame = this; |
| bestData->mDistance = distance; |
| WebCore::IntRect rect = test->ring(this, part); |
| bestData->setMouseBounds(rect); |
| bestData->setNodeBounds(rect); |
| CachedHistory* cachedHistory = history(); |
| switch (direction) { |
| case LEFT: |
| bestData->setLeftDirection(cachedHistory); |
| break; |
| case RIGHT: |
| bestData->setRightDirection(cachedHistory); |
| break; |
| case UP: |
| bestData->setUpDirection(cachedHistory); |
| break; |
| case DOWN: |
| bestData->setDownDirection(cachedHistory); |
| break; |
| default: |
| ASSERT(0); |
| } |
| } |
| } |
| } |
| } |
| |
| void CachedFrame::finishInit() |
| { |
| CachedNode* lastCached = lastNode(); |
| lastCached->setLast(); |
| CachedFrame* child = mCachedFrames.begin(); |
| while (child != mCachedFrames.end()) { |
| child->mParent = this; |
| child->finishInit(); |
| child++; |
| } |
| CachedFrame* frameParent; |
| if (mFocusIndex >= 0 && (frameParent = parent())) |
| frameParent->setFocusIndex(indexInParent()); |
| } |
| |
| const CachedNode* CachedFrame::frameDown(const CachedNode* test, |
| const CachedNode* limit, BestData* bestData) const |
| { |
| BestData originalData = *bestData; |
| do { |
| if (moveInFrame(&CachedFrame::frameDown, test, bestData)) |
| continue; |
| BestData testData; |
| if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) |
| continue; |
| if (checkVisited(test, DOWN) == false) |
| continue; |
| size_t parts = test->navableRects(); |
| for (size_t part = 0; part < parts; part++) { |
| testData.setNodeBounds(test->ring(this, part)); |
| if (testData.setDownDirection(history())) |
| continue; |
| int result = framePartCommon(testData, test, bestData); |
| if (result == REJECT_TEST) |
| continue; |
| if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger |
| BestData innerData = testData; |
| frameDown(document(), test, &innerData); |
| if (checkVisited(innerData.mNode, DOWN)) { |
| *bestData = innerData; |
| continue; |
| } |
| } |
| if (checkVisited(test, DOWN)) |
| *bestData = testData; |
| } |
| } while ((test = test->traverseNextNode()) != limit); |
| ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); |
| // does the best contain something (or, is it contained by an area which is not the cursor?) |
| // if so, is the conainer/containee should have been chosen, but wasn't -- so there's a better choice |
| // in the doc list prior to this choice |
| // |
| return bestData->mNode; |
| } |
| |
| const CachedNode* CachedFrame::frameLeft(const CachedNode* test, |
| const CachedNode* limit, BestData* bestData) const |
| { |
| BestData originalData = *bestData; |
| do { |
| if (moveInFrame(&CachedFrame::frameLeft, test, bestData)) |
| continue; |
| BestData testData; |
| if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) |
| continue; |
| if (checkVisited(test, LEFT) == false) |
| continue; |
| size_t parts = test->navableRects(); |
| for (size_t part = 0; part < parts; part++) { |
| testData.setNodeBounds(test->ring(this, part)); |
| if (testData.setLeftDirection(history())) |
| continue; |
| int result = framePartCommon(testData, test, bestData); |
| if (result == REJECT_TEST) |
| continue; |
| if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger |
| BestData innerData = testData; |
| frameLeft(document(), test, &innerData); |
| if (checkVisited(innerData.mNode, LEFT)) { |
| *bestData = innerData; |
| continue; |
| } |
| } |
| if (checkVisited(test, LEFT)) |
| *bestData = testData; |
| } |
| } while ((test = test->traverseNextNode()) != limit); // FIXME ??? left and up should use traversePreviousNode to choose reverse document order |
| ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); |
| return bestData->mNode; |
| } |
| |
| int CachedFrame::frameNodeCommon(BestData& testData, const CachedNode* test, |
| BestData* bestData, BestData* originalData) const |
| { |
| testData.mFrame = this; |
| testData.mNode = test; |
| test->clearCondition(); |
| if (test->disabled()) { |
| testData.mNode->setCondition(CachedNode::DISABLED); |
| return REJECT_TEST; |
| } |
| if (mRoot->scrolledBounds().intersects(test->bounds(this)) == false) { |
| testData.mNode->setCondition(CachedNode::NAVABLE); |
| return REJECT_TEST; |
| } |
| if (mRoot->rootLayer() && !test->isInLayer() |
| && !mRoot->baseUncovered().intersects(test->bounds(this))) { |
| testData.mNode->setCondition(CachedNode::UNDER_LAYER); |
| return REJECT_TEST; |
| } |
| // if (isNavable(test, &testData.mNodeBounds, walk) == false) { |
| // testData.mNode->setCondition(CachedNode::NAVABLE); |
| // return REJECT_TEST; |
| // } |
| // |
| if (test == mRoot->mCursor) { |
| testData.mNode->setCondition(CachedNode::NOT_CURSOR_NODE); |
| return REJECT_TEST; |
| } |
| // if (test->bounds().contains(mRoot->mCursorBounds)) { |
| // testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); |
| // return REJECT_TEST; |
| // } |
| void* par = mRoot->mCursor ? mRoot->mCursor->parentGroup() : NULL; |
| testData.mCursorChild = par ? test->parentGroup() == par : false; |
| if (bestData->mNode == NULL) |
| return TEST_IS_BEST; |
| if (mRoot->mCursor && testData.mNode->parentIndex() != bestData->mNode->parentIndex()) { |
| int cursorParentIndex = mRoot->mCursor->parentIndex(); |
| if (cursorParentIndex >= 0) { |
| if (bestData->mNode->parentIndex() == cursorParentIndex) |
| return REJECT_TEST; |
| if (testData.mNode->parentIndex() == cursorParentIndex) |
| return TEST_IS_BEST; |
| } |
| } |
| if (testData.mNode->parent() == bestData->mNode) { |
| testData.mNode->setCondition(CachedNode::CHILD); |
| return REJECT_TEST; |
| } |
| if (testData.mNode == bestData->mNode->parent()) |
| return TEST_IS_BEST; |
| int testInBest = testData.isContainer(bestData); /* -1 pick best over test, 0 no containership, 1 pick test over best */ |
| if (testInBest == 1) { |
| if (test->isArea() || bestData->mNode->isArea()) |
| return UNDECIDED; |
| bestData->mNode = NULL; // force part tests to be ignored, yet still set up remaining test data for later comparisons |
| return TEST_IS_BEST; |
| } |
| if (testInBest == -1) { |
| testData.mNode->setCondition(CachedNode::OUTSIDE_OF_BEST); |
| return REJECT_TEST; |
| } |
| if (originalData->mNode != NULL) { // test is best case |
| testInBest = testData.isContainer(originalData); |
| if (testInBest == -1) { /* test is inside best */ |
| testData.mNode->setCondition(CachedNode::OUTSIDE_OF_ORIGINAL); |
| return REJECT_TEST; |
| } |
| } |
| return UNDECIDED; |
| } |
| |
| int CachedFrame::framePartCommon(BestData& testData, |
| const CachedNode* test, BestData* bestData) const |
| { |
| if (mRoot->mCursor |
| && testData.bounds().contains(mRoot->mCursorBounds) |
| && !test->wantsKeyEvents()) { |
| testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); |
| return REJECT_TEST; |
| } |
| testData.setDistances(); |
| if (bestData->mNode != NULL) { |
| int compared = compare(testData, *bestData); |
| if (compared == 0 && test->isArea() == false && bestData->mNode->isArea() == false) |
| goto pickTest; |
| if (compared >= 0) |
| return compared; |
| } |
| pickTest: |
| return -1; // pick test |
| } |
| |
| const CachedNode* CachedFrame::frameRight(const CachedNode* test, |
| const CachedNode* limit, BestData* bestData) const |
| { |
| BestData originalData = *bestData; |
| do { |
| if (moveInFrame(&CachedFrame::frameRight, test, bestData)) |
| continue; |
| BestData testData; |
| if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) |
| continue; |
| if (checkVisited(test, RIGHT) == false) |
| continue; |
| size_t parts = test->navableRects(); |
| for (size_t part = 0; part < parts; part++) { |
| testData.setNodeBounds(test->ring(this, part)); |
| if (testData.setRightDirection(history())) |
| continue; |
| int result = framePartCommon(testData, test, bestData); |
| if (result == REJECT_TEST) |
| continue; |
| if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger |
| BestData innerData = testData; |
| frameRight(document(), test, &innerData); |
| if (checkVisited(innerData.mNode, RIGHT)) { |
| *bestData = innerData; |
| continue; |
| } |
| } |
| if (checkVisited(test, RIGHT)) |
| *bestData = testData; |
| } |
| } while ((test = test->traverseNextNode()) != limit); |
| ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); |
| return bestData->mNode; |
| } |
| |
| const CachedNode* CachedFrame::frameUp(const CachedNode* test, |
| const CachedNode* limit, BestData* bestData) const |
| { |
| BestData originalData = *bestData; |
| do { |
| if (moveInFrame(&CachedFrame::frameUp, test, bestData)) |
| continue; |
| BestData testData; |
| if (frameNodeCommon(testData, test, bestData, &originalData) == REJECT_TEST) |
| continue; |
| if (checkVisited(test, UP) == false) |
| continue; |
| size_t parts = test->navableRects(); |
| for (size_t part = 0; part < parts; part++) { |
| testData.setNodeBounds(test->ring(this, part)); |
| if (testData.setUpDirection(history())) |
| continue; |
| int result = framePartCommon(testData, test, bestData); |
| if (result == REJECT_TEST) |
| continue; |
| if (result == 0 && limit == NULL) { // retry all data up to this point, since smaller may have replaced node preferable to larger |
| BestData innerData = testData; |
| frameUp(document(), test, &innerData); |
| if (checkVisited(innerData.mNode, UP)) { |
| *bestData = innerData; |
| continue; |
| } |
| } |
| if (checkVisited(test, UP)) |
| *bestData = testData; |
| } |
| } while ((test = test->traverseNextNode()) != limit); // FIXME ??? left and up should use traversePreviousNode to choose reverse document order |
| ASSERT(mRoot->mCursor == NULL || bestData->mNode != mRoot->mCursor); |
| return bestData->mNode; |
| } |
| |
| const CachedFrame* CachedFrame::hasFrame(const CachedNode* node) const |
| { |
| return node->isFrame() ? &mCachedFrames[node->childFrameIndex()] : NULL; |
| } |
| |
| void CachedFrame::hideCursor() |
| { |
| DBG_NAV_LOGD("mCursorIndex=%d", mCursorIndex); |
| if (mCursorIndex < CURSOR_SET) |
| return; |
| CachedNode& cursor = mCachedNodes[mCursorIndex]; |
| cursor.hideCursor(this); |
| } |
| |
| CachedHistory* CachedFrame::history() const |
| { |
| return mRoot->rootHistory(); |
| } |
| |
| void CachedFrame::init(const CachedRoot* root, int childFrameIndex, |
| WebCore::Frame* frame) |
| { |
| mContents = WebCore::IntRect(0, 0, 0, 0); // fixed up for real in setData() |
| mLocalViewBounds = WebCore::IntRect(0, 0, 0, 0); |
| mViewBounds = WebCore::IntRect(0, 0, 0, 0); |
| mRoot = root; |
| mCursorIndex = CURSOR_UNINITIALIZED; // not explicitly cleared |
| mFocusIndex = -1; |
| mFrame = frame; |
| mParent = NULL; // set up parents after stretchy arrays are set up |
| mIndexInParent = childFrameIndex; |
| } |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| const CachedLayer* CachedFrame::layer(const CachedNode* node) const |
| { |
| if (!node->isInLayer()) |
| return 0; |
| CachedLayer test; |
| test.setCachedNodeIndex(node->index()); |
| return std::lower_bound(mCachedLayers.begin(), mCachedLayers.end(), test); |
| } |
| #endif |
| |
| WebCore::IntRect CachedFrame::localBounds(const CachedNode* node, |
| const WebCore::IntRect& rect) const |
| { |
| DBG_NAV_LOGD("node=%p [%d] rect=(%d,%d,w=%d,h=%d)", |
| node, node->index(), rect.x(), rect.y(), rect.width(), rect.height()); |
| #if USE(ACCELERATED_COMPOSITING) |
| return layer(node)->localBounds(rect); |
| #else |
| return rect; |
| #endif |
| } |
| |
| int CachedFrame::minWorkingHorizontal() const |
| { |
| return history()->minWorkingHorizontal(); |
| } |
| |
| int CachedFrame::minWorkingVertical() const |
| { |
| return history()->minWorkingVertical(); |
| } |
| |
| int CachedFrame::maxWorkingHorizontal() const |
| { |
| return history()->maxWorkingHorizontal(); |
| } |
| |
| int CachedFrame::maxWorkingVertical() const |
| { |
| return history()->maxWorkingVertical(); |
| } |
| |
| const CachedNode* CachedFrame::nextTextField(const CachedNode* start, |
| const CachedFrame** framePtr) const |
| { |
| CachedNode* test; |
| if (start) { |
| test = const_cast<CachedNode*>(start); |
| test++; |
| } else { |
| test = const_cast<CachedNode*>(mCachedNodes.begin()); |
| } |
| while (test != mCachedNodes.end()) { |
| CachedFrame* frame = const_cast<CachedFrame*>(hasFrame(test)); |
| if (frame) { |
| const CachedNode* node |
| = frame->nextTextField(0, framePtr); |
| if (node) |
| return node; |
| } else if (test->isTextInput()) { |
| if (framePtr) |
| *framePtr = this; |
| return test; |
| } |
| test++; |
| } |
| return 0; |
| } |
| |
| bool CachedFrame::moveInFrame(MoveInDirection moveInDirection, |
| const CachedNode* test, BestData* bestData) const |
| { |
| const CachedFrame* frame = hasFrame(test); |
| if (frame == NULL) |
| return false; // if it's not a frame, let the caller have another swing at it |
| const CachedNode* childDoc = frame->validDocument(); |
| if (childDoc == NULL) |
| return true; |
| (frame->*moveInDirection)(childDoc, NULL, bestData); |
| return true; |
| } |
| |
| const WebCore::IntRect& CachedFrame::_navBounds() const |
| { |
| return history()->navBounds(); |
| } |
| |
| SkPicture* CachedFrame::picture(const CachedNode* node) const |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| if (node->isInLayer()) |
| return layer(node)->picture(mRoot->rootLayer()); |
| #endif |
| return mRoot->mPicture; |
| } |
| |
| void CachedFrame::resetClippedOut() |
| { |
| for (CachedNode* test = mCachedNodes.begin(); test != mCachedNodes.end(); test++) |
| { |
| if (test->clippedOut()) { |
| test->setDisabled(false); |
| test->setClippedOut(false); |
| } |
| } |
| for (CachedFrame* frame = mCachedFrames.begin(); frame != mCachedFrames.end(); |
| frame++) { |
| frame->resetClippedOut(); |
| } |
| } |
| |
| void CachedFrame::resetLayers() |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| for (CachedLayer* test = mCachedLayers.begin(); test != mCachedLayers.end(); |
| test++) { |
| test->reset(); |
| } |
| for (CachedFrame* frame = mCachedFrames.begin(); frame != mCachedFrames.end(); |
| frame++) { |
| frame->resetLayers(); |
| } |
| #endif |
| } |
| |
| bool CachedFrame::sameFrame(const CachedFrame* test) const |
| { |
| ASSERT(test); |
| if (mIndexInParent != test->mIndexInParent) |
| return false; |
| if (mIndexInParent == -1) // index within parent's array of children, or -1 if root |
| return true; |
| return mParent->sameFrame(test->mParent); |
| } |
| |
| void CachedFrame::setData() |
| { |
| if (this != mRoot) { |
| mViewBounds = mLocalViewBounds; |
| mViewBounds.intersect(mRoot->mViewBounds); |
| } |
| int x, y; |
| if (parent() == NULL) |
| x = y = 0; |
| else { |
| x = mLocalViewBounds.x(); |
| y = mLocalViewBounds.y(); |
| } |
| mContents.setX(x); |
| mContents.setY(y); |
| CachedFrame* child = mCachedFrames.begin(); |
| while (child != mCachedFrames.end()) { |
| child->setData(); |
| child++; |
| } |
| } |
| |
| bool CachedFrame::setCursor(WebCore::Frame* frame, WebCore::Node* node, |
| int x, int y) |
| { |
| if (NULL == node) { |
| const_cast<CachedRoot*>(mRoot)->setCursor(NULL, NULL); |
| return true; |
| } |
| if (mFrame != frame) { |
| for (CachedFrame* testF = mCachedFrames.begin(); testF != mCachedFrames.end(); |
| testF++) { |
| if (testF->setCursor(frame, node, x, y)) |
| return true; |
| } |
| DBG_NAV_LOGD("no frame frame=%p node=%p", frame, node); |
| return false; |
| } |
| bool first = true; |
| CachedNode const * const end = mCachedNodes.end(); |
| do { |
| for (CachedNode* test = mCachedNodes.begin(); test != end; test++) { |
| if (test->nodePointer() != node && first) |
| continue; |
| size_t partMax = test->navableRects(); |
| for (size_t part = 0; part < partMax; part++) { |
| WebCore::IntRect testBounds = test->ring(this, part); |
| if (testBounds.contains(x, y) == false) |
| continue; |
| if (test->isCursor()) { |
| DBG_NAV_LOGD("already set? test=%d frame=%p node=%p x=%d y=%d", |
| test->index(), frame, node, x, y); |
| return false; |
| } |
| const_cast<CachedRoot*>(mRoot)->setCursor(this, test); |
| return true; |
| } |
| } |
| DBG_NAV_LOGD("moved? frame=%p node=%p x=%d y=%d", frame, node, x, y); |
| } while ((first ^= true) == false); |
| failed: |
| DBG_NAV_LOGD("no match frame=%p node=%p", frame, node); |
| return false; |
| } |
| |
| const CachedNode* CachedFrame::validDocument() const |
| { |
| const CachedNode* doc = document(); |
| return doc != NULL && mViewBounds.isEmpty() == false ? doc : NULL; |
| } |
| |
| bool CachedFrame::BestData::canBeReachedByAnotherDirection() |
| { |
| if (mMajorButt > -MIN_OVERLAP) |
| return false; |
| mMajorButt = -mMajorButt; |
| return mNavOutside; |
| } |
| |
| int CachedFrame::BestData::isContainer(CachedFrame::BestData* other) |
| { |
| int _x = x(); |
| int otherRight = other->right(); |
| if (_x >= otherRight) |
| return 0; // does not intersect |
| int _y = y(); |
| int otherBottom = other->bottom(); |
| if (_y >= otherBottom) |
| return 0; // does not intersect |
| int _right = right(); |
| int otherX = other->x(); |
| if (otherX >= _right) |
| return 0; // does not intersect |
| int _bottom = bottom(); |
| int otherY = other->y(); |
| if (otherY >= _bottom) |
| return 0; // does not intersect |
| int intoX = otherX - _x; |
| int intoY = otherY - _y; |
| int intoRight = otherRight - _right; |
| int intoBottom = otherBottom - _bottom; |
| bool contains = intoX >= 0 && intoY >= 0 && intoRight <= 0 && intoBottom <= 0; |
| if (contains && mNode->partRectsContains(other->mNode)) { |
| // if (mIsArea == false && hasMouseOver()) |
| // other->mMouseOver = mNode; |
| return mNode->isArea() ? 1 : -1; |
| } |
| bool containedBy = intoX <= 0 && intoY <= 0 && intoRight >= 0 && intoBottom >= 0; |
| if (containedBy && other->mNode->partRectsContains(mNode)) { |
| // if (other->mIsArea == false && other->hasMouseOver()) |
| // mMouseOver = other->mNode; |
| return other->mNode->isArea() ? -1 : 1; |
| } |
| return 0; |
| } |
| |
| // distance scale factor factor as a 16.16 scalar |
| SkFixed CachedFrame::BestData::Overlap(int span, int left, int right) |
| { |
| unsigned result; |
| if (left > 0 && left < span && right > span) |
| result = (unsigned) left; |
| else if (right > 0 && right < span && left > span) |
| result = (unsigned) right; |
| else if (left > 0 && right > 0) |
| return SK_Fixed1; |
| else |
| return 0; |
| result = (result << 16) / (unsigned) span; // linear proportion, always less than fixed 1 |
| return (SkFixed) result; |
| // !!! experiment with weight -- enable if overlaps are preferred too much |
| // or reverse weighting if overlaps are preferred to little |
| // return (SkFixed) (result * result >> 16); // but fall off with square |
| } |
| |
| void CachedFrame::BestData::setDistances() |
| { |
| mDistance = abs(mMajorDelta); |
| int sideDistance = mWorkingDelta; |
| if (mWorkingOverlap < SK_Fixed1) { |
| if (mPreferred > 0) |
| sideDistance = mWorkingDelta2; |
| } else if (sideDistance >= 0 && mWorkingDelta2 >=- 0) |
| sideDistance = 0; |
| else { |
| ASSERT(sideDistance <= 0 && mWorkingDelta2 <= 0); |
| if (sideDistance < mWorkingDelta2) |
| sideDistance = mWorkingDelta2; |
| } |
| // if overlap, smaller abs mWorkingDelta is better, smaller abs majorDelta is better |
| // if not overlap, positive mWorkingDelta is better |
| mSideDistance = sideDistance; |
| } |
| |
| bool CachedFrame::BestData::setDownDirection(const CachedHistory* history) |
| { |
| const WebCore::IntRect& navBounds = history->navBounds(); |
| mMajorButt = mNodeBounds.y() - navBounds.bottom(); |
| int testX = mNodeBounds.x(); |
| int testRight = mNodeBounds.right(); |
| setNavOverlap(navBounds.width(), navBounds.right() - testX, |
| testRight - navBounds.x()); |
| if (canBeReachedByAnotherDirection()) { |
| mNode->setCondition(CachedNode::BEST_DIRECTION); |
| return REJECT_TEST; |
| } |
| int inNavTop = mNodeBounds.y() - navBounds.y(); |
| mMajorDelta2 = inNavTop; |
| mMajorDelta = mMajorDelta2 + ((mNodeBounds.height() - |
| navBounds.height()) >> 1); |
| if (mMajorDelta2 <= 1 && mMajorDelta <= 1) { |
| mNode->setCondition(CachedNode::CENTER_FURTHER); // never move up or sideways |
| return REJECT_TEST; |
| } |
| int inNavBottom = navBounds.bottom() - mNodeBounds.bottom(); |
| setNavInclusion(testRight - navBounds.right(), navBounds.x() - testX); |
| bool subsumes = navBounds.height() > 0 && inOrSubsumesNav(); |
| if (inNavTop <= 0 && inNavBottom <= 0 && subsumes && !mNode->wantsKeyEvents()) { |
| mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); |
| return REJECT_TEST; |
| } |
| int maxV = history->maxWorkingVertical(); |
| int minV = history->minWorkingVertical(); |
| setWorkingOverlap(testRight - testX, maxV - testX, testRight - minV); |
| setWorkingInclusion(testRight - maxV, minV - testX); |
| if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavBottom >= 0) { |
| mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); |
| return REJECT_TEST; |
| } |
| mInNav = history->directionChange() && inNavTop >= 0 && |
| inNavBottom > 0 && subsumes; |
| return false; |
| } |
| |
| bool CachedFrame::BestData::setLeftDirection(const CachedHistory* history) |
| { |
| const WebCore::IntRect& navBounds = history->navBounds(); |
| mMajorButt = navBounds.x() - mNodeBounds.right(); |
| int testY = mNodeBounds.y(); |
| int testBottom = mNodeBounds.bottom(); |
| setNavOverlap(navBounds.height(), navBounds.bottom() - testY, |
| testBottom - navBounds.y()); |
| if (canBeReachedByAnotherDirection()) { |
| mNode->setCondition(CachedNode::BEST_DIRECTION); |
| return REJECT_TEST; |
| } |
| int inNavRight = navBounds.right() - mNodeBounds.right(); |
| mMajorDelta2 = inNavRight; |
| mMajorDelta = mMajorDelta2 - ((navBounds.width() - |
| mNodeBounds.width()) >> 1); |
| if (mMajorDelta2 <= 1 && mMajorDelta <= 1) { |
| mNode->setCondition(CachedNode::CENTER_FURTHER); // never move right or sideways |
| return REJECT_TEST; |
| } |
| int inNavLeft = mNodeBounds.x() - navBounds.x(); |
| setNavInclusion(navBounds.y() - testY, testBottom - navBounds.bottom()); |
| bool subsumes = navBounds.width() > 0 && inOrSubsumesNav(); |
| if (inNavLeft <= 0 && inNavRight <= 0 && subsumes && !mNode->wantsKeyEvents()) { |
| mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); |
| return REJECT_TEST; |
| } |
| int maxH = history->maxWorkingHorizontal(); |
| int minH = history->minWorkingHorizontal(); |
| setWorkingOverlap(testBottom - testY, maxH - testY, testBottom - minH); |
| setWorkingInclusion(minH - testY, testBottom - maxH); |
| if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavLeft >= 0) { |
| mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); |
| return REJECT_TEST; |
| } |
| mInNav = history->directionChange() && inNavLeft >= 0 && |
| inNavRight > 0 && subsumes; /* both L/R in or out */ |
| return false; |
| } |
| |
| bool CachedFrame::BestData::setRightDirection(const CachedHistory* history) |
| { |
| const WebCore::IntRect& navBounds = history->navBounds(); |
| mMajorButt = mNodeBounds.x() - navBounds.right(); |
| int testY = mNodeBounds.y(); |
| int testBottom = mNodeBounds.bottom(); |
| setNavOverlap(navBounds.height(), navBounds.bottom() - testY, |
| testBottom - navBounds.y()); |
| if (canBeReachedByAnotherDirection()) { |
| mNode->setCondition(CachedNode::BEST_DIRECTION); |
| return REJECT_TEST; |
| } |
| int inNavLeft = mNodeBounds.x() - navBounds.x(); |
| mMajorDelta2 = inNavLeft; |
| mMajorDelta = mMajorDelta2 + ((mNodeBounds.width() - |
| navBounds.width()) >> 1); |
| if (mMajorDelta2 <= 1 && mMajorDelta <= 1) { |
| mNode->setCondition(CachedNode::CENTER_FURTHER); // never move left or sideways |
| return REJECT_TEST; |
| } |
| int inNavRight = navBounds.right() - mNodeBounds.right(); |
| setNavInclusion(testBottom - navBounds.bottom(), navBounds.y() - testY); |
| bool subsumes = navBounds.width() > 0 && inOrSubsumesNav(); |
| if (inNavLeft <= 0 && inNavRight <= 0 && subsumes && !mNode->wantsKeyEvents()) { |
| mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); |
| return REJECT_TEST; |
| } |
| int maxH = history->maxWorkingHorizontal(); |
| int minH = history->minWorkingHorizontal(); |
| setWorkingOverlap(testBottom - testY, testBottom - minH, maxH - testY); |
| setWorkingInclusion(testBottom - maxH, minH - testY); |
| if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavRight >= 0) { |
| mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); |
| return REJECT_TEST; |
| } |
| mInNav = history->directionChange() && inNavLeft >= 0 && |
| inNavRight > 0 && subsumes; /* both L/R in or out */ |
| return false; |
| } |
| |
| bool CachedFrame::BestData::setUpDirection(const CachedHistory* history) |
| { |
| const WebCore::IntRect& navBounds = history->navBounds(); |
| mMajorButt = navBounds.y() - mNodeBounds.bottom(); |
| int testX = mNodeBounds.x(); |
| int testRight = mNodeBounds.right(); |
| setNavOverlap(navBounds.width(), navBounds.right() - testX, |
| testRight - navBounds.x()); |
| if (canBeReachedByAnotherDirection()) { |
| mNode->setCondition(CachedNode::BEST_DIRECTION); |
| return REJECT_TEST; |
| } |
| int inNavBottom = navBounds.bottom() - mNodeBounds.bottom(); |
| mMajorDelta2 = inNavBottom; |
| mMajorDelta = mMajorDelta2 - ((navBounds.height() - |
| mNodeBounds.height()) >> 1); |
| if (mMajorDelta2 <= 1 && mMajorDelta <= 1) { |
| mNode->setCondition(CachedNode::CENTER_FURTHER); // never move down or sideways |
| return REJECT_TEST; |
| } |
| int inNavTop = mNodeBounds.y() - navBounds.y(); |
| setNavInclusion(navBounds.x() - testX, testRight - navBounds.right()); |
| bool subsumes = navBounds.height() > 0 && inOrSubsumesNav(); |
| if (inNavTop <= 0 && inNavBottom <= 0 && subsumes && !mNode->wantsKeyEvents()) { |
| mNode->setCondition(CachedNode::NOT_ENCLOSING_CURSOR); |
| return REJECT_TEST; |
| } |
| int maxV = history->maxWorkingVertical(); |
| int minV = history->minWorkingVertical(); |
| setWorkingOverlap(testRight - testX, testRight - minV, maxV - testX); |
| setWorkingInclusion(minV - testX, testRight - maxV); |
| if (mWorkingOverlap == 0 && mNavOverlap == 0 && inNavTop >= 0) { |
| mNode->setCondition(CachedNode::OVERLAP_OR_EDGE_FURTHER); |
| return REJECT_TEST; |
| } |
| mInNav = history->directionChange() && inNavTop >= 0 && |
| inNavBottom > 0 && subsumes; /* both L/R in or out */ |
| return false; |
| } |
| |
| void CachedFrame::BestData::setNavInclusion(int left, int right) |
| { |
| // if left and right <= 0, test node is completely in umbra of cursor |
| // prefer leftmost center |
| // if left and right > 0, test node subsumes cursor |
| mNavDelta = left; |
| mNavDelta2 = right; |
| } |
| |
| void CachedFrame::BestData::setNavOverlap(int span, int left, int right) |
| { |
| // if left or right < 0, test node is not in umbra of cursor |
| mNavOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; |
| mNavOverlap = Overlap(span, left, right); // prefer smallest negative left |
| } |
| |
| void CachedFrame::BestData::setWorkingInclusion(int left, int right) |
| { |
| mWorkingDelta = left; |
| mWorkingDelta2 = right; |
| } |
| |
| // distance scale factor factor as a 16.16 scalar |
| void CachedFrame::BestData::setWorkingOverlap(int span, int left, int right) |
| { |
| // if left or right < 0, test node is not in umbra of cursor |
| mWorkingOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; |
| mWorkingOverlap = Overlap(span, left, right); |
| mPreferred = left <= 0 ? 0 : left; |
| } |
| |
| #if DUMP_NAV_CACHE |
| |
| #define DEBUG_PRINT_RECT(prefix, debugName, field) \ |
| { const WebCore::IntRect& r = b->field; \ |
| DUMP_NAV_LOGD("%s DebugTestRect TEST%s_" #debugName "={%d, %d, %d, %d}; //" #field "\n", \ |
| prefix, mFrameName, r.x(), r.y(), r.width(), r.height()); } |
| |
| CachedFrame* CachedFrame::Debug::base() const { |
| CachedFrame* nav = (CachedFrame*) ((char*) this - OFFSETOF(CachedFrame, mDebug)); |
| return nav; |
| } |
| |
| void CachedFrame::Debug::print() const |
| { |
| CachedFrame* b = base(); |
| DEBUG_PRINT_RECT("//", CONTENTS, mContents); |
| DEBUG_PRINT_RECT("", BOUNDS, mLocalViewBounds); |
| DEBUG_PRINT_RECT("//", VIEW, mViewBounds); |
| |
| DUMP_NAV_LOGD("// CachedNode mCachedNodes={ // count=%d\n", b->mCachedNodes.size()); |
| for (CachedNode* node = b->mCachedNodes.begin(); |
| node != b->mCachedNodes.end(); node++) { |
| node->mDebug.print(); |
| const CachedInput* input = b->textInput(node); |
| if (input) |
| input->mDebug.print(); |
| } |
| DUMP_NAV_LOGD("// }; // end of nodes\n"); |
| #if USE(ACCELERATED_COMPOSITING) |
| DUMP_NAV_LOGD("// CachedLayer mCachedLayers={ // count=%d\n", b->mCachedLayers.size()); |
| for (CachedLayer* layer = b->mCachedLayers.begin(); |
| layer != b->mCachedLayers.end(); layer++) { |
| layer->mDebug.print(); |
| } |
| DUMP_NAV_LOGD("// }; // end of layers\n"); |
| #endif // USE(ACCELERATED_COMPOSITING) |
| DUMP_NAV_LOGD("// CachedFrame mCachedFrames={ // count=%d\n", b->mCachedFrames.size()); |
| for (CachedFrame* child = b->mCachedFrames.begin(); |
| child != b->mCachedFrames.end(); child++) |
| { |
| child->mDebug.print(); |
| } |
| DUMP_NAV_LOGD("// }; // end of child frames\n"); |
| DUMP_NAV_LOGD("// void* mFrame=(void*) %p;\n", b->mFrame); |
| DUMP_NAV_LOGD("// CachedFrame* mParent=%p;\n", b->mParent); |
| DUMP_NAV_LOGD("// int mIndexInParent=%d;\n", b->mIndexInParent); |
| DUMP_NAV_LOGD("// const CachedRoot* mRoot=%p;\n", b->mRoot); |
| DUMP_NAV_LOGD("// int mCursorIndex=%d;\n", b->mCursorIndex); |
| DUMP_NAV_LOGD("// int mFocusIndex=%d;\n", b->mFocusIndex); |
| } |
| |
| bool CachedFrame::Debug::validate(const CachedNode* node) const |
| { |
| const CachedFrame* b = base(); |
| if (b->mCachedNodes.size() == 0) |
| return false; |
| if (node >= b->mCachedNodes.begin() && node < b->mCachedNodes.end()) |
| return true; |
| for (const CachedFrame* child = b->mCachedFrames.begin(); |
| child != b->mCachedFrames.end(); child++) |
| if (child->mDebug.validate(node)) |
| return true; |
| return false; |
| } |
| |
| #undef DEBUG_PRINT_RECT |
| |
| #endif |
| |
| } |