diff options
Diffstat (limited to 'WebKit/android/nav')
-rw-r--r-- | WebKit/android/nav/CacheBuilder.cpp | 3076 | ||||
-rw-r--r-- | WebKit/android/nav/CacheBuilder.h | 272 | ||||
-rw-r--r-- | WebKit/android/nav/CachedDebug.h | 74 | ||||
-rw-r--r-- | WebKit/android/nav/CachedFrame.cpp | 1318 | ||||
-rw-r--r-- | WebKit/android/nav/CachedFrame.h | 228 | ||||
-rw-r--r-- | WebKit/android/nav/CachedHistory.cpp | 203 | ||||
-rw-r--r-- | WebKit/android/nav/CachedHistory.h | 90 | ||||
-rw-r--r-- | WebKit/android/nav/CachedNode.cpp | 346 | ||||
-rw-r--r-- | WebKit/android/nav/CachedNode.h | 235 | ||||
-rw-r--r-- | WebKit/android/nav/CachedNodeType.h | 41 | ||||
-rw-r--r-- | WebKit/android/nav/CachedPrefix.h | 46 | ||||
-rw-r--r-- | WebKit/android/nav/CachedRoot.cpp | 1087 | ||||
-rw-r--r-- | WebKit/android/nav/CachedRoot.h | 115 | ||||
-rw-r--r-- | WebKit/android/nav/FindCanvas.cpp | 467 | ||||
-rw-r--r-- | WebKit/android/nav/FindCanvas.h | 207 | ||||
-rw-r--r-- | WebKit/android/nav/SelectText.cpp | 263 | ||||
-rw-r--r-- | WebKit/android/nav/SelectText.h | 42 | ||||
-rw-r--r-- | WebKit/android/nav/WebView.cpp | 2322 |
18 files changed, 10432 insertions, 0 deletions
diff --git a/WebKit/android/nav/CacheBuilder.cpp b/WebKit/android/nav/CacheBuilder.cpp new file mode 100644 index 0000000..9150830 --- /dev/null +++ b/WebKit/android/nav/CacheBuilder.cpp @@ -0,0 +1,3076 @@ +/* + * Copyright 2006, 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 APPLE COMPUTER, INC. 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 "CachedNode.h" +#include "CachedRoot.h" +#include "Document.h" +#include "EventNames.h" +#include "EventTargetNode.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "FrameView.h" +//#include "GraphicsContext.h" +#include "HTMLAreaElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLMapElement.h" +#include "HTMLNames.h" +#include "HTMLOptionElement.h" +#include "HTMLSelectElement.h" +#include "InlineTextBox.h" +#include "KURL.h" +#include "PluginView.h" +#include "RenderImage.h" +#include "RenderInline.h" +#include "RenderListBox.h" +#include "RenderSkinCombo.h" +#include "RenderTextControl.h" +#include "RenderWidget.h" +#include "SkCanvas.h" +#include "SkPoint.h" +#include "Text.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreViewBridge.h" +#include "Widget.h" +#include <wtf/unicode/Unicode.h> + +#ifdef DUMP_NAV_CACHE_USING_PRINTF + FILE* gNavCacheLogFile = NULL; + android::Mutex gWriteLogMutex; +#endif + +#include "CacheBuilder.h" + +#define MINIMUM_FOCUSABLE_WIDTH 3 +#define MINIMUM_FOCUSABLE_HEIGHT 3 +#define MAXIMUM_FOCUS_RING_COUNT 32 + +namespace android { + +CacheBuilder* CacheBuilder::Builder(Frame* frame) { + return &((FrameLoaderClientAndroid*) frame->loader()->client())->getCacheBuilder(); +} + +Frame* CacheBuilder::FrameAnd(CacheBuilder* cacheBuilder) { + FrameLoaderClientAndroid* loader = (FrameLoaderClientAndroid*) + ((char*) cacheBuilder - OFFSETOF(FrameLoaderClientAndroid, m_cacheBuilder)); + return loader->getFrame(); +} + +Frame* CacheBuilder::FrameAnd(const CacheBuilder* cacheBuilder) { + FrameLoaderClientAndroid* loader = (FrameLoaderClientAndroid*) + ((char*) cacheBuilder - OFFSETOF(FrameLoaderClientAndroid, m_cacheBuilder)); + return loader->getFrame(); +} + +#if DUMP_NAV_CACHE + +#define DEBUG_BUFFER_SIZE 256 +#define DEBUG_WRAP_SIZE 150 +#define DEBUG_WRAP_MAX 170 + +Frame* CacheBuilder::Debug::frameAnd() const { + CacheBuilder* nav = (CacheBuilder*) ((char*) this - OFFSETOF(CacheBuilder, mDebug)); + return CacheBuilder::FrameAnd(nav); +} + +void CacheBuilder::Debug::attr(const AtomicString& name, const AtomicString& value) { + if (name.isNull() || name.isEmpty() || value.isNull() || value.isEmpty()) + return; + uChar(name.characters(), name.length(), false); + print("="); + wideString(value.characters(), value.length(), false); + print(" "); +} + +void CacheBuilder::Debug::comma(const char* str) { + print(str); + print(", "); +} + +int CacheBuilder::Debug::flowBoxes(RenderFlow* flow, int ifIndex, int indent) { + char scratch[256]; + const InlineFlowBox* box = flow->firstLineBox(); + if (box == NULL) + return ifIndex; + do { + newLine(); + int i = snprintf(scratch, sizeof(scratch), "// render flow:%p" + " box:%p%.*s", flow, box, indent, " "); + for (; box; box = box->nextFlowBox()) { + i += snprintf(&scratch[i], sizeof(scratch) - i, + " [%d]:{%d, %d, %d, %d}", ++ifIndex, + box->xPos(), box->yPos(), box->width(), box->height()); + if (ifIndex % 4 == 0) + break; + } + print(scratch); + } while (box); + RenderObject const * const end = flow->lastChild(); + if (end == NULL) + return ifIndex; + indent += 2; + if (indent > 8) + indent = 8; + for (const RenderObject* renderer = flow->firstChild(); renderer != end; + renderer = renderer->nextSibling()) + { + if (renderer->isInlineFlow()) + ifIndex = flowBoxes((RenderFlow*) renderer, ifIndex, indent); + } + return ifIndex; +} + +void CacheBuilder::Debug::flush() { + int len; + do { + int limit = mIndex; + if (limit < DEBUG_WRAP_SIZE) + return; + if (limit < DEBUG_WRAP_MAX) + len = limit; + else { + limit = DEBUG_WRAP_MAX; + len = DEBUG_WRAP_SIZE; + while (len < limit) { + char test = mBuffer[len]; + if (test < '/' || (test > '9' && test < 'A') || (test > 'Z' && test < 'a') || test > 'z') + break; + len++; + } + while (mBuffer[len] == '\\' || mBuffer[len] == '"') + len++; + } + const char* prefix = mPrefix; + if (prefix[0] == '\"') { + // see if we're inside a quote + int quoteCount = 0; + for (int index = 0; index < len; index++) { + if (mBuffer[index] == '\\') { + index++; + continue; + } + if (mBuffer[index] == '\n') { + quoteCount = 0; + continue; + } + if (mBuffer[index] == '"') + quoteCount++; + } + if ((quoteCount & 1) == 0) + prefix = "\n"; + } + DUMP_NAV_LOGD("%.*s", len, mBuffer); + int copy = mIndex - len; + strcpy(mBuffer, prefix); + memcpy(&mBuffer[strlen(prefix)], &mBuffer[len], copy); + mIndex = strlen(prefix) + copy; + } while (true); +} + +void CacheBuilder::Debug::frameName(char*& namePtr, const char* max) const { + if (namePtr >= max) + return; + Frame* frame = frameAnd(); + Frame* parent = frame->tree()->parent(); + if (parent) + Builder(parent)->mDebug.frameName(namePtr, max); + const AtomicString& name = frame->tree()->name(); + if (name.length() == 0) + return; + unsigned index = 0; + if (name.startsWith(AtomicString("opener"))) + index = 6; + for (; index < name.length(); index++) { + char ch = name[index]; + if (ch <= ' ') + ch = '_'; + if (WTF::isASCIIAlphanumeric(ch) || ch == '_') + *namePtr++ = ch; + } +} + +void CacheBuilder::Debug::frames() { + Frame* frame = frameAnd(); + Document* doc = frame->document(); + if (doc == NULL) + return; + bool top = frame->tree()->parent() == NULL; + if (top) { +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gWriteLogMutex.lock(); + ASSERT(gNavCacheLogFile == NULL); + gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a"); +#endif + groups(); + } + Frame* child = frame->tree()->firstChild(); + bool hasChild = child != NULL; + if (top && hasChild) + DUMP_NAV_LOGD("\nnamespace TEST_SPACE {\n\n"); + while (child) { + Builder(child)->mDebug.frames(); + child = child->tree()->nextSibling(); + } + if (hasChild) { + child = frame->tree()->firstChild(); + while (child) { + char childName[256]; + char* childNamePtr = childName; + Builder(child)->mDebug.frameName(childNamePtr, childNamePtr + sizeof(childName) - 1); + *childNamePtr = '\0'; + if (child == frame->tree()->firstChild()) + DUMP_NAV_LOGD("DebugTestFrameGroup TEST%s_GROUP[] = {\n", childName); + Frame* next = child->tree()->nextSibling(); + Document* doc = child->document(); + if (doc != NULL) { + RenderObject* renderer = doc->renderer(); + if (renderer != NULL) { + RenderLayer* layer = renderer->enclosingLayer(); + if (layer != NULL) { + DUMP_NAV_LOGD("{ "); + DUMP_NAV_LOGD("TEST%s_RECTS, ", childName); + DUMP_NAV_LOGD("TEST%s_RECT_COUNT, ", childName); + DUMP_NAV_LOGD("TEST%s_RECTPARTS, ", childName); + DUMP_NAV_LOGD("TEST%s_BOUNDS,\n", childName); + DUMP_NAV_LOGD("TEST%s_WIDTH, ", childName); + DUMP_NAV_LOGD("TEST%s_HEIGHT,\n", childName); + DUMP_NAV_LOGD("0, 0, 0, 0,\n"); + DUMP_NAV_LOGD("TEST%s_FOCUS, ", childName); + Frame* grandChild = child->tree()->firstChild(); + if (grandChild) { + char grandChildName[256]; + char* grandChildNamePtr = grandChildName; + Builder(grandChild)->mDebug.frameName(grandChildNamePtr, + grandChildNamePtr + sizeof(grandChildName) - 1); + *grandChildNamePtr = '\0'; + DUMP_NAV_LOGD("TEST%s_GROUP, ", grandChildName); + DUMP_NAV_LOGD("sizeof(TEST%s_GROUP) / sizeof(DebugTestFrameGroup), ", grandChildName); + } else + DUMP_NAV_LOGD("NULL, 0, "); + DUMP_NAV_LOGD("\"%s\"\n", childName); + DUMP_NAV_LOGD("}%c\n", next ? ',' : ' '); + } + } + } + child = next; + } + DUMP_NAV_LOGD("};\n"); + } + if (top) { + if (hasChild) + DUMP_NAV_LOGD("\n} // end of namespace\n\n"); + char name[256]; + char* frameNamePtr = name; + frameName(frameNamePtr, frameNamePtr + sizeof(name) - 1); + *frameNamePtr = '\0'; + DUMP_NAV_LOGD("DebugTestFrameGroup TEST%s_GROUP = {\n", name); + DUMP_NAV_LOGD("TEST%s_RECTS, ", name); + DUMP_NAV_LOGD("TEST%s_RECT_COUNT, ", name); + DUMP_NAV_LOGD("TEST%s_RECTPARTS, ", name); + DUMP_NAV_LOGD("TEST%s_BOUNDS,\n", name); + DUMP_NAV_LOGD("TEST%s_WIDTH, ", name); + DUMP_NAV_LOGD("TEST%s_HEIGHT,\n", name); + DUMP_NAV_LOGD("TEST%s_MAX_H, ", name); + DUMP_NAV_LOGD("TEST%s_MIN_H, ", name); + DUMP_NAV_LOGD("TEST%s_MAX_V, ", name); + DUMP_NAV_LOGD("TEST%s_MIN_V,\n", name); + DUMP_NAV_LOGD("TEST%s_FOCUS, ", name); + if (hasChild) { + child = frame->tree()->firstChild(); + char childName[256]; + char* childNamePtr = childName; + Builder(child)->mDebug.frameName(childNamePtr, childNamePtr + sizeof(childName) - 1); + *childNamePtr = '\0'; + DUMP_NAV_LOGD("TEST_SPACE::TEST%s_GROUP, ", childName); + DUMP_NAV_LOGD("sizeof(TEST_SPACE::TEST%s_GROUP) / sizeof(DebugTestFrameGroup), \n" ,childName); + } else + DUMP_NAV_LOGD("NULL, 0, "); + DUMP_NAV_LOGD("\"%s\"\n", name); + DUMP_NAV_LOGD("};\n"); +#ifdef DUMP_NAV_CACHE_USING_PRINTF + if (gNavCacheLogFile) + fclose(gNavCacheLogFile); + gNavCacheLogFile = NULL; + gWriteLogMutex.unlock(); +#endif + } +} + +void CacheBuilder::Debug::init(char* buffer, size_t size) { + mBuffer = buffer; + mBufferSize = size; + mIndex = 0; + mPrefix = ""; +} + +void CacheBuilder::Debug::groups() { + Frame* frame = frameAnd(); + Frame* child = frame->tree()->firstChild(); + bool hasChild = child != NULL; + if (frame->tree()->parent() == NULL && hasChild) + DUMP_NAV_LOGD("namespace TEST_SPACE {\n\n"); + while (child) { + Builder(child)->mDebug.groups(); + child = child->tree()->nextSibling(); + } + if (frame->tree()->parent() == NULL && hasChild) + DUMP_NAV_LOGD("\n} // end of namespace\n\n"); + Document* doc = frame->document(); + char name[256]; + char* frameNamePtr = name; + frameName(frameNamePtr, frameNamePtr + sizeof(name) - 1); + *frameNamePtr = '\0'; + if (doc == NULL) { + DUMP_NAV_LOGD("// %s has no document\n", name); + return; + } + RenderObject* renderer = doc->renderer(); + if (renderer == NULL) { + DUMP_NAV_LOGD("// %s has no renderer\n", name); + return; + } + RenderLayer* layer = renderer->enclosingLayer(); + if (layer == NULL) { + DUMP_NAV_LOGD("// %s has no enclosingLayer\n", name); + return; + } + Node* node = doc; + Node* focus = doc->focusedNode(); + bool atLeastOne = false; + do { + if ((atLeastOne |= isFocusable(node)) != false) + break; + } while ((node = node->traverseNextNode()) != NULL); + int focusIndex = -1; + if (atLeastOne == false) { + DUMP_NAV_LOGD("#define TEST%s_RECTS NULL\n", name); + DUMP_NAV_LOGD("static int TEST%s_RECT_COUNT = 0; // no focusable nodes\n", name); + DUMP_NAV_LOGD("#define TEST%s_RECTPARTS NULL\n", name); + } else { + node = doc; + int count = 1; + DUMP_NAV_LOGD("static DebugTestNode TEST%s_RECTS[] = {\n", name); + do { + String properties; + EventTargetNode* elementTarget = node->isEventTargetNode() ? + (EventTargetNode*) node : NULL; + if (elementTarget) { + if (elementTarget->getEventListener(eventNames().clickEvent)) + properties.append("ONCLICK | "); + if (elementTarget->getEventListener(eventNames().mousedownEvent)) + properties.append("MOUSEDOWN | "); + if (elementTarget->getEventListener(eventNames().mouseupEvent)) + properties.append("MOUSEUP | "); + if (elementTarget->getEventListener(eventNames().mouseoverEvent)) + properties.append("MOUSEOVER | "); + if (elementTarget->getEventListener(eventNames().mouseoutEvent)) + properties.append("MOUSEOUT | "); + if (elementTarget->getEventListener(eventNames().keydownEvent)) + properties.append("KEYDOWN | "); + if (elementTarget->getEventListener(eventNames().keyupEvent)) + properties.append("KEYUP | "); + } + if (CacheBuilder::HasFrame(node)) + properties.append("FRAME | "); + if (focus == node) { + properties.append("FOCUS | "); + focusIndex = count; + } + if (node->isKeyboardFocusable(NULL)) + properties.append("KEYBOARD_FOCUSABLE | "); + if (node->isMouseFocusable()) + properties.append("MOUSE_FOCUSABLE | "); + if (node->isFocusable()) + properties.append("SIMPLE_FOCUSABLE | "); + if (properties.isEmpty()) + properties.append("0"); + else + properties.truncate(properties.length() - 3); + IntRect rect = node->getRect(); + if (node->hasTagName(HTMLNames::areaTag)) + rect = Builder(frame)->getAreaRect(static_cast<HTMLAreaElement*>(node)); + char buffer[DEBUG_BUFFER_SIZE]; + memset(buffer, 0, sizeof(buffer)); + mBuffer = buffer; + mBufferSize = sizeof(buffer); + mPrefix = "\"\n\""; + mIndex = snprintf(buffer, sizeof(buffer), "{{%d, %d, %d, %d}, ", rect.x(), rect.y(), + rect.width(), rect.height()); + localName(node); + uChar(properties.characters(), properties.length(), false); + print(", "); + int parentIndex = ParentIndex(node, count, node->parentNode()); + char scratch[256]; + snprintf(scratch, sizeof(scratch), "%d", parentIndex); + comma(scratch); + Element* element = static_cast<Element*>(node); + if (node->isElementNode() && element->hasID()) + wideString(element->getIDAttribute()); + else if (node->isTextNode()) { + #if 01 // set to one to abbreviate text that can be omitted from the address detection code + if (rect.isEmpty() && node->textContent().length() > 100) { + wideString(node->textContent().characters(), 100, false); + snprintf(scratch, sizeof(scratch), "/* + %d bytes */", + node->textContent().length() - 100); + print(scratch); + } else + #endif + wideString(node->textContent().characters(), node->textContent().length(), true); + } else if (node->hasTagName(HTMLNames::aTag) || + node->hasTagName(HTMLNames::areaTag)) + { + HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(node); + wideString(anchor->href()); + } else if (node->hasTagName(HTMLNames::imgTag)) { + HTMLImageElement* image = static_cast<HTMLImageElement*>(node); + wideString(image->src()); + } else + print("\"\""); + RenderObject* renderer = node->renderer(); + int tabindex = node->isElementNode() ? node->tabIndex() : 0; + if (renderer) { + const IntRect& absB = renderer->absoluteBoundingBoxRect(); + snprintf(scratch, sizeof(scratch), ", {%d, %d, %d, %d}, %s" + ", %d},",absB.x(), absB.y(), absB.width(), absB.height(), + renderer->hasOverflowClip() ? "true" : "false", tabindex); + print(scratch); + } else + print(", {0, 0, 0, 0}, false, 0},"); + + flush(); + snprintf(scratch, sizeof(scratch), "// %d: ", count); + mPrefix = "\n// "; + print(scratch); + //print(renderer ? renderer->information().ascii() : "NO_RENDER_INFO"); + if (node->isElementNode()) { + Element* element = static_cast<Element*>(node); + NamedAttrMap* attrs = element->attributes(); + unsigned length = attrs->length(); + if (length > 0) { + newLine(); + print("// attr: "); + for (unsigned i = 0; i < length; i++) { + Attribute* a = attrs->attributeItem(i); + attr(a->localName(), a->value()); + } + } + } + if (renderer) + renderTree(renderer, 0, node, count); + count++; + newLine(); + } while ((node = node->traverseNextNode()) != NULL); + DUMP_NAV_LOGD("}; // focusables = %d\n", count - 1); + DUMP_NAV_LOGD("\n"); + DUMP_NAV_LOGD("static int TEST%s_RECT_COUNT = %d;\n\n", name, count - 1); + // look for rects with multiple parts + node = doc; + count = 1; + bool hasRectParts = false; + int globalOffsetX, globalOffsetY; + GetGlobalOffset(frame, &globalOffsetX, &globalOffsetY); + do { + IntRect rect; + bool _isFocusable = isFocusable(node) || (node->isTextNode() + && node->getRect().isEmpty() == false + ); + int nodeIndex = count++; + if (_isFocusable == false) + continue; + RenderObject* renderer = node->renderer(); + if (renderer == NULL) + continue; + WTF::Vector<IntRect> rects; + IntRect clipBounds = IntRect(0, 0, INT_MAX, INT_MAX); + IntRect focusBounds = IntRect(0, 0, INT_MAX, INT_MAX); + IntRect* rectPtr = &focusBounds; + if (node->isTextNode()) { + Text* textNode = (Text*) node; + if (CacheBuilder::ConstructTextRects(textNode, 0, textNode, + INT_MAX, globalOffsetX, globalOffsetY, rectPtr, + clipBounds, &rects) == false) + continue; + } else { + IntRect nodeBounds = node->getRect(); + if (CacheBuilder::ConstructPartRects(node, nodeBounds, rectPtr, + globalOffsetX, globalOffsetY, &rects) == false) + continue; + } + unsigned arraySize = rects.size(); + if (arraySize > 1 || (arraySize == 1 && (rectPtr->width() != rect.width())) || + rectPtr->height() != rect.height()) { + if (hasRectParts == false) { + DUMP_NAV_LOGD("static DebugTestRectPart TEST%s_RECTPARTS[] = {\n", name); + hasRectParts = true; + } + if (node->isTextNode() == false) { + unsigned rectIndex = 0; + for (; rectIndex < arraySize; rectIndex++) { + rectPtr = &rects.at(rectIndex); + DUMP_NAV_LOGD("{ %d, %d, %d, %d, %d }, // %d\n", nodeIndex, + rectPtr->x(), rectPtr->y(), rectPtr->width(), + rectPtr->height(), rectIndex + 1); + } + } else { + RenderText* renderText = (RenderText*) node->renderer(); + InlineTextBox* textBox = renderText->firstTextBox(); + unsigned rectIndex = 0; + while (textBox) { + int renderX, renderY; + renderText->absolutePosition(renderX, renderY); + IntRect rect = textBox->selectionRect(renderX, renderY, 0, INT_MAX); + mIndex = 0; + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, "{ %d, %d, %d, %d, %d", + nodeIndex, rect.x(), rect.y(), rect.width(), rect.height()); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d", + textBox->len(), textBox->selectionHeight(), textBox->selectionTop()); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d", + textBox->spaceAdd(), textBox->start(), textBox->textPos()); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d, %d, %d, %d", + textBox->xPos(), textBox->yPos(), textBox->width(), textBox->height()); + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, ", %d }, // %d ", + textBox->baseline(), ++rectIndex); + wideString(node->textContent().characters() + textBox->start(), textBox->len(), true); + DUMP_NAV_LOGD("%.*s\n", mIndex, mBuffer); + textBox = textBox->nextTextBox(); + } + } + } + } while ((node = node->traverseNextNode()) != NULL); + if (hasRectParts) + DUMP_NAV_LOGD("{0}\n};\n\n"); + else + DUMP_NAV_LOGD("static DebugTestRectPart* TEST%s_RECTPARTS = NULL;\n", name); + } + int contentsWidth = layer->width(); + int contentsHeight = layer->height(); + DUMP_NAV_LOGD("static int TEST%s_FOCUS = %d;\n", name, focusIndex); + DUMP_NAV_LOGD("static int TEST%s_WIDTH = %d;\n", name, contentsWidth); + DUMP_NAV_LOGD("static int TEST%s_HEIGHT = %d;\n", name, contentsHeight); +} + +bool CacheBuilder::Debug::isFocusable(Node* node) { + if (node->hasTagName(HTMLNames::areaTag)) + return true; + if (node->renderer() == false) + return false; + if (node->isKeyboardFocusable(NULL)) + return true; + if (node->isMouseFocusable()) + return true; + if (node->isFocusable()) + return true; + if (node->isEventTargetNode()) + return true; + if (CacheBuilder::AnyIsClick(node)) + return false; + if (CacheBuilder::HasTriggerEvent(node)) + return true; + return false; +} + +void CacheBuilder::Debug::localName(Node* node) { + const AtomicString& local = node->localName(); + if (node->isTextNode()) + print("\"#text\""); + else + wideString(local.characters(), local.length(), false); + print(", "); +} + +void CacheBuilder::Debug::newLine(int indent) { + if (mPrefix[0] != '\n') + print(&mPrefix[0], 1); + flush(); + int lastnewline = mIndex - 1; + while (lastnewline >= 0 && mBuffer[lastnewline] != '\n') + lastnewline--; + lastnewline++; + char* buffer = mBuffer; + if (lastnewline > 0) { + DUMP_NAV_LOGD("%.*s", lastnewline, buffer); + mIndex -= lastnewline; + buffer += lastnewline; + } + size_t prefixLen = strlen(mPrefix); + int minPrefix = prefixLen - 1; + while (minPrefix >= 0 && mPrefix[minPrefix] != '\n') + minPrefix--; + minPrefix = prefixLen - minPrefix - 1; + if (mIndex > minPrefix) + DUMP_NAV_LOGD("%.*s\n", mIndex, buffer); + mIndex = 0; + setIndent(indent); +} + +int CacheBuilder::Debug::ParentIndex(Node* node, int count, Node* parent) +{ + if (parent == NULL) + return -1; + ASSERT(node != parent); + int result = count; + Node* previous = node; + do { + result--; + previous = previous->traversePreviousNode(); + } while (previous && previous != parent); + if (previous != NULL) + return result; + result = count; + do { + result++; + } while ((node = node->traverseNextNode()) != NULL && node != parent); + if (node != NULL) + return result; + ASSERT(0); + return -1; +} + +void CacheBuilder::Debug::print(const char* name) { + print(name, strlen(name)); +} + +void CacheBuilder::Debug::print(const char* name, unsigned len) { + do { + if (mIndex + len >= DEBUG_BUFFER_SIZE) + flush(); + int copyLen = mIndex + len < DEBUG_BUFFER_SIZE ? + len : DEBUG_BUFFER_SIZE - mIndex; + memcpy(&mBuffer[mIndex], name, copyLen); + mIndex += copyLen; + name += copyLen; + len -= copyLen; + } while (len > 0); + mBuffer[mIndex] = '\0'; +} + +void CacheBuilder::Debug::setIndent(int indent) +{ + char scratch[64]; + snprintf(scratch, sizeof(scratch), "%.*s", indent, + " "); + print(scratch); +} + +void CacheBuilder::Debug::renderTree(RenderObject* renderer, int indent, + Node* child, int count) +{ + char scratch[256]; + Node* node = renderer->node(); + if (node != child) { + count = ParentIndex(child, count, node); + if (renderer->isRenderBlock() == false) + goto tryParent; + RenderBlock* renderBlock = (RenderBlock*) renderer; + if (renderBlock->hasColumns() == false) + goto tryParent; + Vector<IntRect>* rects = renderBlock->columnRects(); + newLine(indent); + snprintf(scratch, sizeof(scratch), "// render parent=%d", count); + print(scratch); + for (size_t x = 0; x < rects->size(); x++) { + const IntRect& rect = rects->at(x); + snprintf(scratch, sizeof(scratch), "(%d,%d,%d,%d) ", rect.x(), + rect.y(), rect.width(), rect.height()); + print(scratch); + } + } + { + newLine(indent); + RenderStyle* style = renderer->style(); + EVisibility vis = style->visibility(); + ASSERT(vis == VISIBLE || vis == HIDDEN || vis == COLLAPSE); + snprintf(scratch, sizeof(scratch), + "// render style visible:%s opacity:%g width:%d height:%d" + " hasBackground:%s isInlineFlow:%s isBlockFlow:%s" + " textOverflow:%s", + vis == VISIBLE ? "visible" : vis == HIDDEN ? "hidden" : "collapse", + style->opacity(), renderer->width(), renderer->height(), + style->hasBackground() ? "true" : "false", + renderer->isInlineFlow() ? "true" : "false", + renderer->isBlockFlow() ? "true" : "false", + style->textOverflow() ? "true" : "false" + ); + print(scratch); + newLine(indent); + const IntRect& oRect = renderer->overflowRect(true); + const IntRect& cRect = renderer->getOverflowClipRect(0,0); + snprintf(scratch, sizeof(scratch), + "// render xPos:%d yPos:%d overflowRect:{%d, %d, %d, %d} " + " getOverflowClipRect:{%d, %d, %d, %d} ", + renderer->xPos(), renderer->yPos(), + oRect.x(), oRect.y(), oRect.width(), oRect.height(), + cRect.x(), cRect.y(), cRect.width(), cRect.height() + ); + print(scratch); + if (renderer->isInlineFlow()) { + RenderFlow* renderFlow = (RenderFlow*) renderer; + int ifIndex = 0; + flowBoxes(renderFlow, ifIndex, 0); + } + } +tryParent: + RenderObject* parent = renderer->parent(); + if (parent) + renderTree(parent, indent + 2, node, count); +} + +void CacheBuilder::Debug::uChar(const UChar* name, unsigned len, bool hex) { + const UChar* end = name + len; + bool wroteHex = false; + while (name < end) { + unsigned ch = *name++; + if (ch == '\t' || ch == '\n' || ch == '\r' || ch == 0xa0) + ch = ' '; + if (ch < ' ' || ch == 0x7f) { + if (hex) { + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, "\\x%02x", ch); + wroteHex = true; + } else + mBuffer[mIndex++] = '?'; + } else if (ch >= 0x80) { + if (hex) { + if (ch < 0x800) + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, + "\\x%02x\\x%02x", ch >> 6 | 0xc0, (ch & 0x3f) | 0x80); + else + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, + "\\x%02x\\x%02x\\x%02x", ch >> 12 | 0xe0, + (ch >> 6 & 0x3f) | 0x80, (ch & 0x3f) | 0x80); + wroteHex = true; + } else + mBuffer[mIndex++] = '?'; + } else { + if (wroteHex && WTF::isASCIIHexDigit((UChar) ch)) + mIndex += snprintf(&mBuffer[mIndex], mBufferSize - mIndex, + "\" \""); + else if (ch == '"' || ch == '\\') + mBuffer[mIndex++] = '\\'; + mBuffer[mIndex++] = ch; + wroteHex = false; + } + if (mIndex + 1 >= DEBUG_BUFFER_SIZE) + flush(); + } + flush(); +} + +void CacheBuilder::Debug::validateFrame() { + Frame* frame = frameAnd(); + Page* page = frame->page(); + ASSERT(page); + ASSERT((int) page > 0x10000); + Frame* child = frame->tree()->firstChild(); + while (child) { + Builder(child)->mDebug.validateFrame(); + child = child->tree()->nextSibling(); + } +} + +void CacheBuilder::Debug::wideString(const UChar* chars, int length, bool hex) { + if (length == 0) + print("\"\""); + else { + print("\""); + uChar(chars, length, hex); + print("\""); + } +} + +void CacheBuilder::Debug::wideString(const String& str) { + wideString(str.characters(), str.length(), false); +} + +#endif // DUMP_NAV_CACHE + +CacheBuilder::CacheBuilder() +{ + mLastKnownFocus = NULL; + mAllowableTypes = ALL_CACHEDNODETYPES; +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gNavCacheLogFile = NULL; +#endif +} + +void CacheBuilder::adjustForColumns(const ClipColumnTracker& track, + CachedNode* node, IntRect* bounds) +{ + int x = 0; + int y = 0; + int tx = track.mBounds.x(); + int ty = track.mBounds.y(); + int columnGap = track.mColumnGap; + size_t limit = track.mColumns->size(); + for (size_t index = 0; index < limit; index++) { + IntRect column = track.mColumns->at(index); + column.move(tx, ty); + IntRect test = *bounds; + test.move(x, y); + if (column.contains(test)) { + if ((x | y) == 0) + return; + *bounds = test; + node->move(x, y); + return; + } + int xOffset = column.width() + columnGap; + x += track.mDirection == LTR ? xOffset : -xOffset; + y -= column.height(); + } +} + +bool CacheBuilder::AnyChildIsClick(Node* node) +{ + Node* child = node->firstChild(); + while (child != NULL) { + if (child->isEventTargetNode()) { + EventTargetNode* target = (EventTargetNode*) child; + if (target->isFocusable() || + target->getEventListener(eventNames().clickEvent) || + target->getEventListener(eventNames().mousedownEvent) || + target->getEventListener(eventNames().mouseupEvent) || + target->getEventListener(eventNames().keydownEvent) || + target->getEventListener(eventNames().keyupEvent)) + return true; + } + if (AnyChildIsClick(child)) + return true; + child = child->nextSibling(); + } + return false; +} + +bool CacheBuilder::AnyIsClick(Node* node) +{ + if (node->hasTagName(HTMLNames::bodyTag)) + return AnyChildIsClick(node); + EventTargetNode* target = (EventTargetNode*) node; + if (target->getEventListener(eventNames().mouseoverEvent) == NULL && + target->getEventListener(eventNames().mouseoutEvent) == NULL && + target->getEventListener(eventNames().keydownEvent) == NULL && + target->getEventListener(eventNames().keyupEvent) == NULL) + return false; + if (target->getEventListener(eventNames().clickEvent)) + return false; + if (target->getEventListener(eventNames().mousedownEvent)) + return false; + if (target->getEventListener(eventNames().mouseupEvent)) + return false; + return AnyChildIsClick(node); +} + +void CacheBuilder::buildCache(CachedRoot* root) +{ + Frame* frame = FrameAnd(this); + mLastKnownFocus = NULL; + m_areaBoundsMap.clear(); + BuildFrame(frame, frame, root, (CachedFrame*) root); + root->finishInit(); // set up frame parent pointers, child pointers + setData((CachedFrame*) root); +} + +static Node* OneAfter(Node* node) +{ + Node* parent = node; + Node* sibling = NULL; + while ((parent = parent->parentNode()) != NULL) { + sibling = parent->nextSibling(); + if (sibling != NULL) + break; + } + return sibling; +} + +// return true if this renderer is really a pluinview, and it wants +// key-events (i.e. focus) +static bool checkForPluginViewThatWantsFocus(RenderObject* renderer) { + if (renderer->isWidget()) { + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (widget && widget->isPluginView()) { + PluginView* pv = static_cast<PluginView*>(widget); + // check if this plugin really wants key events (TODO) + return true; + } + } + return false; +} + +// when new focus is found, push it's parent on a stack + // as long as more focii are found with the same (grand) parent, note it + // (which only requires retrieving the last parent on the stack) +// when the parent's last child is found, pop the stack +// different from Tracker in that Tracker only pushes focii with children + +// making this work with focus - child focus - grandchild focus is tricky +// if I keep the generation number, I may be able to more quickly determine that +// a node is a grandchild of the focus's parent +// this additionally requires being able to find the grandchild's parent + +// keep nodes that are focusable +void CacheBuilder::BuildFrame(Frame* root, Frame* frame, + CachedRoot* cachedRoot, CachedFrame* cachedFrame) +{ + WTF::Vector<Tracker> tracker(1); + { + Tracker* baseTracker = tracker.data(); // sentinel + bzero(baseTracker, sizeof(Tracker)); + baseTracker->mCachedNodeIndex = -1; + } + WTF::Vector<ClipColumnTracker> clipTracker(1); + { + ClipColumnTracker* baseTracker = clipTracker.data(); // sentinel + bzero(baseTracker, sizeof(ClipColumnTracker)); + } + WTF::Vector<TabIndexTracker> tabIndexTracker(1); + { + TabIndexTracker* baseTracker = tabIndexTracker.data(); // sentinel + bzero(baseTracker, sizeof(TabIndexTracker)); + } +#if DUMP_NAV_CACHE + char* frameNamePtr = cachedFrame->mDebug.mFrameName; + Builder(frame)->mDebug.frameName(frameNamePtr, frameNamePtr + + sizeof(cachedFrame->mDebug.mFrameName) - 1); + *frameNamePtr = '\0'; + int nodeIndex = 1; +#endif + NodeWalk walk; + Document* doc = frame->document(); + Node* parent = doc; + CachedNode cachedParentNode; + cachedParentNode.init(parent); +#if DUMP_NAV_CACHE + cachedParentNode.mDebug.mNodeIndex = nodeIndex; +#endif + cachedFrame->add(cachedParentNode); + Node* node = parent; + int cacheIndex = 1; + Node* focused = doc->focusedNode(); + if (focused) { + setLastFocus(focused); + cachedRoot->setFocusBounds(mLastKnownFocusBounds); + } + int globalOffsetX, globalOffsetY; + GetGlobalOffset(frame, &globalOffsetX, &globalOffsetY); + while (walk.mMore || (node = node->traverseNextNode()) != NULL) { +#if DUMP_NAV_CACHE + nodeIndex++; +#endif + Tracker* last = &tracker.last(); + int lastChildIndex = cachedFrame->size() - 1; + while (node == last->mLastChild) { + if (CleanUpContainedNodes(cachedFrame, last, lastChildIndex)) + cacheIndex--; + tracker.removeLast(); + lastChildIndex = last->mCachedNodeIndex; + last = &tracker.last(); + } + if (node == last->mParentLastChild) + last->mParentLastChild = NULL; + do { + const ClipColumnTracker* lastClip = &clipTracker.last(); + if (node != lastClip->mLastChild) + break; + clipTracker.removeLast(); + } while (true); + do { + const TabIndexTracker* lastTabIndex = &tabIndexTracker.last(); + if (node != lastTabIndex->mLastChild) + break; + tabIndexTracker.removeLast(); + } while (true); + Frame* child = HasFrame(node); + CachedNode cachedNode; + if (child != NULL) { + if (child->document() == NULL) + continue; + RenderObject* nodeRenderer = node->renderer(); + if (nodeRenderer != NULL && nodeRenderer->style()->visibility() == HIDDEN) + continue; + CachedFrame cachedChild; + cachedChild.init(cachedRoot, cacheIndex, child); + int childFrameIndex = cachedFrame->childCount(); + cachedFrame->addFrame(cachedChild); + cachedNode.init(node); + cachedNode.setIndex(cacheIndex++); + cachedNode.setChildFrameIndex(childFrameIndex); +#if DUMP_NAV_CACHE + cachedNode.mDebug.mNodeIndex = nodeIndex; + cachedNode.mDebug.mParentGroupIndex = Debug::ParentIndex( + node, nodeIndex, NULL); +#endif + cachedFrame->add(cachedNode); + CachedFrame* childPtr = cachedFrame->lastChild(); + BuildFrame(root, child, cachedRoot, childPtr); + continue; + } + int tabIndex = node->tabIndex(); + Node* lastChild = node->lastChild(); + if (tabIndex <= 0) + tabIndex = tabIndexTracker.last().mTabIndex; + else if (tabIndex > 0 && lastChild) { + DBG_NAV_LOGD("tabIndex=%d node=%p", tabIndex, node); + tabIndexTracker.grow(tabIndexTracker.size() + 1); + TabIndexTracker& indexTracker = tabIndexTracker.last(); + indexTracker.mTabIndex = tabIndex; + indexTracker.mLastChild = OneAfter(lastChild); + } + RenderObject* nodeRenderer = node->renderer(); + bool isTransparent = false; + bool hasFocusRing = true; + if (nodeRenderer != NULL) { + RenderStyle* style = nodeRenderer->style(); + if (style->visibility() == HIDDEN) + continue; + if (nodeRenderer->isImage()) { // set all the area elements to have a link to their images + RenderImage* image = static_cast<RenderImage*>(nodeRenderer); + HTMLMapElement* map = image->imageMap(); + if (map) { + Node* node; + for (node = map->firstChild(); node; + node = node->traverseNextNode(map)) { + if (!node->hasTagName(HTMLNames::areaTag)) + continue; + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); + m_areaBoundsMap.set(area, image); + } + } + } + isTransparent = style->hasBackground() == false; +#ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR + hasFocusRing = style->tapHighlightColor().alpha() > 0; +#endif + } + bool more = walk.mMore; + walk.reset(); + // GetGlobalBounds(node, &bounds, false); + bool computeFocusRings = false; + bool hasClip = false; + bool hasMouseOver = false; + bool isAnchor = false; + bool isArea = node->hasTagName(HTMLNames::areaTag); + bool isInput = false; + bool isPassword = false; + bool isTextArea = false; + bool isTextField = false; + bool isRtlText = false; + bool isUnclipped = false; + bool isFocus = node == focused; + bool takesFocus = false; + bool wantsKeyEvents = false; + int maxLength = -1; + int textSize = 12; + int columnGap = 0; + TextDirection direction = LTR; + String name; + String exported; + CachedNodeType type = NORMAL_CACHEDNODETYPE; + IntRect bounds; + IntRect absBounds; + WTF::Vector<IntRect>* columns = NULL; + int minimumFocusableWidth = MINIMUM_FOCUSABLE_WIDTH; + int minimumFocusableHeight = MINIMUM_FOCUSABLE_HEIGHT; + if (isArea) { + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); + bounds = getAreaRect(area); + bounds.move(globalOffsetX, globalOffsetY); + absBounds = bounds; + isUnclipped = true; // FIXME: areamaps require more effort to detect + // assume areamaps are always visible for now + takesFocus = true; + goto keepNode; + } + if (nodeRenderer == NULL) + continue; + + // some common setup + absBounds = nodeRenderer->absoluteBoundingBoxRect(); + absBounds.move(globalOffsetX, globalOffsetY); + hasClip = nodeRenderer->hasOverflowClip(); + + if (checkForPluginViewThatWantsFocus(nodeRenderer)) { + bounds = absBounds; + isUnclipped = true; + takesFocus = true; + wantsKeyEvents = true; + goto keepNode; + } + if (nodeRenderer->isRenderBlock()) { + RenderBlock* renderBlock = (RenderBlock*) nodeRenderer; + if (renderBlock->hasColumns()) { + columns = renderBlock->columnRects(); +#ifdef ANDROID_EXPOSE_COLUMN_GAP + columnGap = renderBlock->columnGap(); +#endif + direction = renderBlock->style()->direction(); + } + } + if ((hasClip != false || columns != NULL) && lastChild) { + clipTracker.grow(clipTracker.size() + 1); + ClipColumnTracker& clip = clipTracker.last(); + clip.mBounds = absBounds; + clip.mLastChild = OneAfter(lastChild); + clip.mNode = node; + clip.mColumns = columns; + clip.mColumnGap = columnGap; + clip.mHasClip = hasClip; + clip.mDirection = direction; + if (columns != NULL) { + const IntRect& oRect = nodeRenderer->overflowRect(true); + clip.mBounds.move(oRect.x(), oRect.y()); + } + } + if (node->isTextNode() && mAllowableTypes != NORMAL_CACHEDNODETYPE) { + if (last->mSomeParentTakesFocus) // don't look at text inside focusable node + continue; + CachedNodeType checkType; + if (isFocusableText(&walk, more, node, &checkType, + &exported) == false) + continue; + #if DUMP_NAV_CACHE + { + char buffer[DEBUG_BUFFER_SIZE]; + mDebug.init(buffer, sizeof(buffer)); + mDebug.print("text link found: "); + mDebug.wideString(exported); + DUMP_NAV_LOGD("%s\n", buffer); + } + #endif + type = (CachedNodeType) checkType; + // !!! test ! is the following line correctly needed for frames to work? + cachedNode.init(node); + const ClipColumnTracker& clipTrack = clipTracker.last(); + const IntRect& clip = clipTrack.mHasClip ? clipTrack.mBounds : + IntRect(0, 0, INT_MAX, INT_MAX); + if (ConstructTextRects((WebCore::Text*) node, walk.mStart, + (WebCore::Text*) walk.mFinalNode, walk.mEnd, globalOffsetX, + globalOffsetY, &bounds, clip, &cachedNode.focusRings()) == false) + continue; + absBounds = bounds; + cachedNode.setBounds(bounds); + if (bounds.width() < MINIMUM_FOCUSABLE_WIDTH) + continue; + if (bounds.height() < MINIMUM_FOCUSABLE_HEIGHT) + continue; + computeFocusRings = true; + isUnclipped = true; // FIXME: to hide or partially occlude synthesized links, each + // focus ring will also need the offset and length of characters + // used to produce it + goto keepTextNode; + } + if (node->hasTagName(WebCore::HTMLNames::inputTag)) { + HTMLInputElement* input = (HTMLInputElement*) node; + if (input->inputType() == HTMLInputElement::FILE) + continue; + isInput = true; + isTextField = input->isTextField(); + isPassword = input->inputType() == HTMLInputElement::PASSWORD; + maxLength = input->maxLength(); + name = String(input->name().string()); + isUnclipped = isTransparent; // can't detect if this is drawn on top (example: deviant.com login parts) + } else if (node->hasTagName(HTMLNames::textareaTag)) + isTextArea = true; + else if (node->hasTagName(HTMLNames::aTag)) { + const HTMLAnchorElement* anchorNode = + (const HTMLAnchorElement*) node; + if (!anchorNode->isFocusable() && !HasTriggerEvent(node)) + continue; + EventTargetNode* target = (EventTargetNode*) node; + if (target->disabled()) + continue; + hasMouseOver = target->getEventListener(eventNames().mouseoverEvent); + isAnchor = true; + KURL href = anchorNode->href(); + if (!href.isEmpty() && !href.protocolIs("javascript")) + // Set the exported string for all non-javascript anchors. + exported = href.string(); + } + if (isTextField || isTextArea) { + RenderTextControl* renderText = + static_cast<RenderTextControl*>(nodeRenderer); + if (isFocus) + cachedRoot->setSelection(renderText->selectionStart(), renderText->selectionEnd()); + exported = String(renderText->text()); + // FIXME: Would it be better to use (float) size()? + // FIXME: Are we sure there will always be a style and font, and it's correct? + RenderStyle* style = nodeRenderer->style(); + if (style) { + isUnclipped |= !style->hasAppearance(); + textSize = style->fontSize(); + isRtlText = style->direction() == RTL || + style->textAlign() == WebCore::RIGHT || + style->textAlign() == WebCore::WEBKIT_RIGHT; + } + minimumFocusableWidth += 4; + minimumFocusableHeight += 4; + } + takesFocus = true; + if (isAnchor) { + bounds = absBounds; + } else { + bool isFocusable = node->isKeyboardFocusable(NULL) || + node->isMouseFocusable() || node->isFocusable(); + if (isFocusable == false) { + if (node->isEventTargetNode() == false) + continue; + EventTargetNode* eventTargetNode = (EventTargetNode*) node; + if (eventTargetNode->disabled()) + continue; + bool overOrOut = HasOverOrOut(node); + bool hasTrigger = HasTriggerEvent(node); + if (overOrOut == false && hasTrigger == false) + continue; + takesFocus = hasTrigger; + } + bounds = node->getRect(); + // For Bank of America site + if (isTextField && nodeRenderer->paddingLeft() > 100) { + int paddingLeft = nodeRenderer->paddingLeft(); + int paddingTop = nodeRenderer->paddingTop(); + int x = bounds.x() + paddingLeft; + int y = bounds.y() + paddingTop; + int width = bounds.width() - paddingLeft - nodeRenderer->paddingRight(); + int height = bounds.height() - paddingTop - nodeRenderer->paddingBottom(); + bounds.setLocation(IntPoint(x, y)); + bounds.setSize(IntSize(width, height)); + } + if (bounds.width() < minimumFocusableWidth) + continue; + if (bounds.height() < minimumFocusableHeight) + continue; + bounds.move(globalOffsetX, globalOffsetY); + } + computeFocusRings = true; + keepNode: + cachedNode.init(node); + if (computeFocusRings == false) { + cachedNode.setBounds(bounds); + cachedNode.focusRings().append(bounds); + } else if (ConstructPartRects(node, bounds, cachedNode.boundsPtr(), + globalOffsetX, globalOffsetY, &cachedNode.focusRings()) == false) + continue; + keepTextNode: + IntRect clip = hasClip ? bounds : absBounds; + size_t clipIndex = clipTracker.size(); + if (clipTracker.last().mNode == node) + clipIndex -= 1; + while (--clipIndex > 0) { + const ClipColumnTracker& clipTrack = clipTracker.at(clipIndex); + if (clipTrack.mHasClip == false) { + adjustForColumns(clipTrack, &cachedNode, &absBounds); + continue; + } + const IntRect& parentClip = clipTrack.mBounds; + if (hasClip == false && isAnchor) + clip = parentClip; + else + clip.intersect(parentClip); + hasClip = true; + } + if (hasClip && cachedNode.clip(clip) == false) { + cachedNode.setBounds(clip); + cachedNode.focusRings().append(clip); + isUnclipped = true; + } + cachedNode.setNavableRects(); + cachedNode.setChildFrameIndex(-1); + cachedNode.setExport(exported); + cachedNode.setHasFocusRing(hasFocusRing); + cachedNode.setHasMouseOver(hasMouseOver); + cachedNode.setHitBounds(absBounds); + cachedNode.setIndex(cacheIndex); + cachedNode.setIsAnchor(isAnchor); + cachedNode.setIsArea(isArea); + cachedNode.setIsFocus(isFocus); + cachedNode.setIsInput(isInput); + cachedNode.setIsPassword(isPassword); + cachedNode.setIsRtlText(isRtlText); + cachedNode.setIsTextArea(isTextArea); + cachedNode.setIsTextField(isTextField); + cachedNode.setIsTransparent(isTransparent); + cachedNode.setIsUnclipped(isUnclipped); + cachedNode.setMaxLength(maxLength); + cachedNode.setName(name); + cachedNode.setParentIndex(last->mCachedNodeIndex); + if (last->mParentLastChild == NULL) + last->mParentLastChild = OneAfter(node->parentNode()->lastChild()); + cachedNode.setParentGroup(last->mParentLastChild); + cachedNode.setTabIndex(tabIndex); + cachedNode.setTextSize(textSize); + cachedNode.setType(type); + cachedNode.setWantsKeyEvents(wantsKeyEvents); +#if DUMP_NAV_CACHE + cachedNode.mDebug.mNodeIndex = nodeIndex; + cachedNode.mDebug.mParentGroupIndex = Debug::ParentIndex( + node, nodeIndex, (Node*) cachedNode.parentGroup()); +#endif + cachedFrame->add(cachedNode); + { + int lastIndex = cachedFrame->size() - 1; + if (node == focused) { + CachedNode* cachedNodePtr = cachedFrame->getIndex(lastIndex); + cachedRoot->setCachedFocus(cachedFrame, cachedNodePtr); + } + if (lastChild != NULL) { + tracker.grow(tracker.size() + 1); + Tracker& working = tracker.last(); + working.mCachedNodeIndex = lastIndex; + working.mLastChild = OneAfter(lastChild); + working.mParentLastChild = OneAfter(node->parentNode()->lastChild()); + last = &tracker.at(tracker.size() - 2); + working.mSomeParentTakesFocus = last->mSomeParentTakesFocus | takesFocus; + } + } + cacheIndex++; + } + while (tracker.size() > 1) { + Tracker* last = &tracker.last(); + int lastChildIndex = cachedFrame->size() - 1; + if (CleanUpContainedNodes(cachedFrame, last, lastChildIndex)) + cacheIndex--; + tracker.removeLast(); + } +} + +bool CacheBuilder::CleanUpContainedNodes(CachedFrame* cachedFrame, + const Tracker* last, int lastChildIndex) +{ + // if outer is body, disable outer + // or if there's more than one inner, disable outer + // or if inner is keyboard focusable, disable outer + // else disable inner by removing it + int childCount = lastChildIndex - last->mCachedNodeIndex; + if (childCount == 0) + return false; + CachedNode* lastCached = cachedFrame->getIndex(last->mCachedNodeIndex); + Node* lastNode = (Node*) lastCached->nodePointer(); + if ((childCount > 1 && lastNode->hasTagName(HTMLNames::selectTag) == false) || + lastNode->hasTagName(HTMLNames::bodyTag) || + lastNode->hasTagName(HTMLNames::formTag)) { + lastCached->setBounds(IntRect(0, 0, 0, 0)); + lastCached->focusRings().clear(); + lastCached->setNavableRects(); + return false; + } + CachedNode* onlyChildCached = cachedFrame->lastNode(); + Node* onlyChild = (Node*) onlyChildCached->nodePointer(); + bool outerIsMouseMoveOnly = + lastNode->isKeyboardFocusable(NULL) == false && + lastNode->isMouseFocusable() == false && + lastNode->isFocusable() == false && + lastNode->isEventTargetNode() == true && + HasOverOrOut(lastNode) == true && + HasTriggerEvent(lastNode) == false; + if (cachedFrame->focusIndex() == lastChildIndex) + cachedFrame->setFocusIndex(last->mCachedNodeIndex); + if (onlyChildCached->parent() == lastCached) + onlyChildCached->setParentIndex(lastCached->parentIndex()); + if (outerIsMouseMoveOnly || onlyChild->isKeyboardFocusable(NULL)) + *lastCached = *onlyChildCached; + cachedFrame->removeLast(); + return true; +} + +Node* CacheBuilder::currentFocus() const +{ + Frame* frame = FrameAnd(this); + Document* doc = frame->document(); + if (doc != NULL) { + Node* focus = doc->focusedNode(); + if (focus != NULL) + return focus; + } + Frame* child = frame->tree()->firstChild(); + while (child) { + CacheBuilder* cacheBuilder = Builder(child); + Node* focus = cacheBuilder->currentFocus(); + if (focus) + return focus; + child = child->tree()->nextSibling(); + } + return NULL; +} + +static bool strCharCmp(const char* matches, const UChar* test, int wordLength, + int wordCount) +{ + for (int index = 0; index < wordCount; index++) { + for (int inner = 0; inner < wordLength; inner++) { + if (matches[inner] != test[inner]) { + matches += wordLength; + goto next; + } + } + return true; +next: + ; + } + return false; +} + +static const int stateTwoLetter[] = { + 0x02060c00, // A followed by: [KLRSZ] + 0x00000000, // B + 0x00084001, // C followed by: [AOT] + 0x00000014, // D followed by: [CE] + 0x00000000, // E + 0x00001800, // F followed by: [LM] + 0x00100001, // G followed by: [AU] + 0x00000100, // H followed by: [I] + 0x00002809, // I followed by: [ADLN] + 0x00000000, // J + 0x01040000, // K followed by: [SY] + 0x00000001, // L followed by: [A] + 0x000ce199, // M followed by: [ADEHINOPST] + 0x0120129c, // N followed by: [CDEHJMVY] + 0x00020480, // O followed by: [HKR] + 0x00420001, // P followed by: [ARW] + 0x00000000, // Q + 0x00000100, // R followed by: [I] + 0x0000000c, // S followed by: [CD] + 0x00802000, // T followed by: [NX] + 0x00080000, // U followed by: [T] + 0x00080101, // V followed by: [AIT] + 0x01200101 // W followed by: [AIVY] +}; + +static const char firstIndex[] = { + 0, 5, 5, 8, 10, 10, 12, 14, + 15, 19, 19, 21, 22, 32, 40, 43, + 46, 46, 47, 49, 51, 52, 55, 59 +}; + +// from http://infolab.stanford.edu/~manku/bitcount/bitcount.html +#define TWO(c) (0x1u << (c)) +#define MASK(c) (((unsigned int)(-1)) / (TWO(TWO(c)) + 1u)) +#define COUNT(x,c) ((x) & MASK(c)) + (((x) >> (TWO(c))) & MASK(c)) + +int bitcount (unsigned int n) +{ + n = COUNT(n, 0); + n = COUNT(n, 1); + n = COUNT(n, 2); + n = COUNT(n, 3); + return COUNT(n, 4); +} + +#undef TWO +#undef MASK +#undef COUNT + +static bool isUnicodeSpace(UChar ch) +{ + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == 0xa0; +} + +static bool validZip(int stateIndex, const UChar* zipPtr) +{ + static const struct { + char mLow; + char mHigh; + char mException1; + char mException2; + } zipRange[] = { + { 99, 99, -1, -1 }, // AK Alaska + { 35, 36, -1, -1 }, // AL Alabama + { 71, 72, -1, -1 }, // AR Arkansas + { 96, 96, -1, -1 }, // AS American Samoa + { 85, 86, -1, -1 }, // AZ Arizona + { 90, 96, -1, -1 }, // CA California + { 80, 81, -1, -1 }, // CO Colorado + { 6, 6, -1, -1 }, // CT Connecticut + { 20, 20, -1, -1 }, // DC District of Columbia + { 19, 19, -1, -1 }, // DE Delaware + { 32, 34, -1, -1 }, // FL Florida + { 96, 96, -1, -1 }, // FM Federated States of Micronesia + { 30, 31, -1, -1 }, // GA Georgia + { 96, 96, -1, -1 }, // GU Guam + { 96, 96, -1, -1 }, // HI Hawaii + { 50, 52, -1, -1 }, // IA Iowa + { 83, 83, -1, -1 }, // ID Idaho + { 60, 62, -1, -1 }, // IL Illinois + { 46, 47, -1, -1 }, // IN Indiana + { 66, 67, 73, -1 }, // KS Kansas + { 40, 42, -1, -1 }, // KY Kentucky + { 70, 71, -1, -1 }, // LA Louisiana + { 1, 2, -1, -1 }, // MA Massachusetts + { 20, 21, -1, -1 }, // MD Maryland + { 3, 4, -1, -1 }, // ME Maine + { 96, 96, -1, -1 }, // MH Marshall Islands + { 48, 49, -1, -1 }, // MI Michigan + { 55, 56, -1, -1 }, // MN Minnesota + { 63, 65, -1, -1 }, // MO Missouri + { 96, 96, -1, -1 }, // MP Northern Mariana Islands + { 38, 39, -1, -1 }, // MS Mississippi + { 55, 56, -1, -1 }, // MT Montana + { 27, 28, -1, -1 }, // NC North Carolina + { 58, 58, -1, -1 }, // ND North Dakota + { 68, 69, -1, -1 }, // NE Nebraska + { 3, 4, -1, -1 }, // NH New Hampshire + { 7, 8, -1, -1 }, // NJ New Jersey + { 87, 88, 86, -1 }, // NM New Mexico + { 88, 89, 96, -1 }, // NV Nevada + { 10, 14, 0, 6 }, // NY New York + { 43, 45, -1, -1 }, // OH Ohio + { 73, 74, -1, -1 }, // OK Oklahoma + { 97, 97, -1, -1 }, // OR Oregon + { 15, 19, -1, -1 }, // PA Pennsylvania + { 6, 6, 0, 9 }, // PR Puerto Rico + { 96, 96, -1, -1 }, // PW Palau + { 2, 2, -1, -1 }, // RI Rhode Island + { 29, 29, -1, -1 }, // SC South Carolina + { 57, 57, -1, -1 }, // SD South Dakota + { 37, 38, -1, -1 }, // TN Tennessee + { 75, 79, 87, 88 }, // TX Texas + { 84, 84, -1, -1 }, // UT Utah + { 22, 24, 20, -1 }, // VA Virginia + { 6, 9, -1, -1 }, // VI Virgin Islands + { 5, 5, -1, -1 }, // VT Vermont + { 98, 99, -1, -1 }, // WA Washington + { 53, 54, -1, -1 }, // WI Wisconsin + { 24, 26, -1, -1 }, // WV West Virginia + { 82, 83, -1, -1 } // WY Wyoming + }; + + int zip = zipPtr[0] - '0'; + zip *= 10; + zip += zipPtr[1] - '0'; + int low = zipRange[stateIndex].mLow; + int high = zipRange[stateIndex].mHigh; + if (zip >= low && zip <= high) + return true; + if (zip == zipRange[stateIndex].mException1) + return true; + if (zip == zipRange[stateIndex].mException2) + return true; + return false; +} + +#define MAX_PLACE_NAME_LENGTH 25 // the longest allowable one word place name + +CacheBuilder::FoundState CacheBuilder::FindAddress(const UChar* chars, unsigned length, int* start, int* end) +{ + FindState addressState; + FindReset(&addressState); + addressState.mWords[0] = addressState.mStarts[0] = chars; + FoundState state = FindPartialAddress(chars, chars, length, &addressState); + if (state == FOUND_PARTIAL && addressState.mProgress == ZIP_CODE && + addressState.mNumberCount == 0) { + addressState.mProgress = FIND_STREET; + state = FindPartialAddress(NULL, NULL, 0, &addressState); + } + *start = addressState.mStartResult; + *end = addressState.mEndResult; + return state; +} + +CacheBuilder::FoundState CacheBuilder::FindPartialAddress(const UChar* baseChars, + const UChar* chars, unsigned length, FindState* s) +{ + // lower case letters are optional; trailing asterisk is optional 's' + static char const* const longStreetNames[] = { + "\x04" "LleY" "\x04" "NneX" "\x05" "RCade" "\x05" "VEnue" "\x06" "LAMEDA", // A + "\x04" "aYoU" "\x04" "eaCH" "\x03" "eND" "\x05" "LuFf*" "\x05" "oTtoM" + "\x08" "ouLeVarD" "\x05" "Ranch" "\x05" "RidGe" "\x05" "RooK*" + "\x04" "urG*" "\x05" "YPass" "\x07" "roadWAY", // B + "\x05" "AMINO" + "\x03" "amP" "\x05" "anYoN" "\x03" "aPE" "\x07" "auSeWaY" "\x06" "enTeR*" + "\x06" "IRcle*" "\x05" "LiFf*" "\x03" "LuB" "\x05" "oMmoN" "\x06" "ORner*" + "\x05" "ouRSE" "\x05" "ourT*" "\x04" "oVe*" "\x04" "ReeK" "\x07" "REScent" + "\x04" "ReST" "\x07" "ROSSING" "\x08" "ROSSROAD" "\x04" "URVe" + "\x05" "AMINO" "\x06" "IRCULO" "\x07" "REScent", // C + "\x03" "aLe" "\x02" "aM" "\x05" "iVide" "\x05" "Rive*", // D + "\x06" "STate*" "\x09" "XPresswaY" "\x09" "XTension*", // E + "\x04" "ALL*" "\x04" "eRrY" "\x05" "ieLD*" "\x04" "LaT*" "\x04" "oRD*" + "\x05" "oReST" "\x05" "oRGe*" "\x04" "oRK*" "\x03" "orT" "\x06" "reeWaY", // F + "\x06" "arDeN*" "\x06" "aTeWaY" "\x04" "LeN*" "\x05" "ReeN*" "\x05" "RoVe*", // G + "\x06" "arBoR*" "\x04" "aVeN" "\x06" "eighTS" "\x06" "ighWaY" "\x04" "iLl*" + "\x05" "OLloW", // H + "\x04" "NLeT" "\x06" "Sland*" "\x03" "SLE", // I + "\x08" "unCTion*", // J + "\x03" "eY*" "\x05" "NoLl*", // K + "\x04" "aKe*" "\x03" "AND" "\x06" "aNDinG" "\x03" "aNe" "\x05" "iGhT*" + "\x03" "oaF" "\x04" "oCK*" "\x04" "oDGe" "\x03" "OOP", // L + "\x03" "ALL" "\x05" "aNoR*" "\x06" "eaDoW*" "\x03" "EWS" "\x04" "iLl*" + "\x06" "iSsioN" "\x07" "oTorWaY" "\x04" "ounT" "\x08" "ounTaiN*", // M + "\x03" "eCK", // N + "\x06" "RCHard" "\x03" "VAL" "\x07" "verPASs", // O + "\x04" "ARK*" "\x07" "arKWaY*" "\x03" "ASS" "\x06" "aSsaGE" "\x03" "ATH" + "\x03" "IKE" "\x04" "iNE*" "\x04" "Lace" "\x05" "LaiN*" "\x04" "LaZa" + "\x05" "oinT*" "\x04" "oRT*" "\x06" "Rairie" "\x06" "RIVADA", // P + NULL, + "\x05" "ADiaL" "\x03" "AMP" "\x04" "aNCH" "\x05" "aPiD*" + "\x03" "eST" + "\x05" "iDGe*" "\x04" "IVer" "\x04" "oaD*" "\x04" "ouTE" "\x02" "OW" + "\x02" "UE" "\x02" "UN", // R + "\x05" "HoaL*" "\x05" "HoRe*" "\x05" "KyWaY" "\x06" "PrinG*" "\x04" "PUR*" + "\x06" "Quare*" "\x06" "TAtion" "\x08" "TRAvenue" "\x05" "TReaM" + "\x06" "Treet*" "\x05" "uMmiT" "\x07" "PeeDWaY", // S + "\x06" "ERrace" "\x09" "hRoughWaY" "\x04" "RaCE" "\x04" "RAcK" "\x09" "RaFficwaY" + "\x04" "RaiL" "\x05" "UNneL" "\x07" "urnPiKE", // T + "\x08" "nderPASs" "\x05" "Nion*", // U + "\x06" "aLleY*" "\x06" "IAduct" "\x04" "ieW*" "\x07" "iLlaGe*" "\x04" "iLle" + "\x04" "ISta", // V + "\x04" "ALK*" "\x03" "ALL" "\x03" "AY*" "\x04" "eLl*", // W + "\x03" "ING" "\x02" "RD", // X + }; + + static char const* const longStateNames[] = { + "\x8e" "la" "\x85" "bama" "\x02" "\x84" "ska" "\x01" "\x8f" "merican Samoa" "\x04" + "\x91" "r" "\x86" "izona" "\x05" "\x87" "kansas" "\x03", + NULL, + "\x8b" "alifornia" "\x06" "\x95" "o" "\x87" "lorado" "\x07" "\x8a" "nnecticut" "\x08", + "\x89" "elaware" "\x0a" "\x95" "istrict of Columbia" "\x09", + NULL, + "\x9f" "ederated States of Micronesia" "\x0c" "\x88" "lorida" "\x0b", + "\x85" "uam" "\x0e" "\x88" "eorgia" "\x0d", + "\x87" "awaii" "\x0f", + "\x86" "daho" "\x11" "\x89" "llinois" "\x12" "\x88" "ndiana" "\x13" "\x85" + "owa" "\x10", + NULL, + "\x87" "ansas" "\x14" "\x89" "entucky" "\x15", + "\x8a" "ouisiana" "\x16", + "\x86" "aine" "\x19" "\x99" "ar" "\x8e" "shall Islands" "\x1a" "\x86" "yland" "\x18" + "\x8e" "assachusetts" "\x17" "\x93" "i" "\x87" "chigan" "\x1b" + "\x88" "nnesota" "\x1c" "\x93" "iss" "\x88" "issippi" "\x1f" "\x85" + "ouri" "\x1d" "\x88" "ontana" "\x20", + "\x90" "e" "\x87" "braska" "\x23" "\x85" "vada" "\x27" "\xa5" "ew " "\x8a" + "Hampshire" "\x24" "\x87" "Jersey" "\x25" "\x87" "Mexico" "\x26" + "\x85" "York" "\x28" "\x98" "orth " "\x89" "Carolina" "\x21" "\x87" + "Dakota" "\x22" "\x99" "orthern Mariana Islands" "\x1e", + "\x85" "hio" "\x29" "\x89" "klahoma" "\x2a" "\x87" "regon" "\x2b", + "\x86" "alau" "\x2e" "\x8d" "ennsylvania" "\x2c" "\x8c" "uerto Rico" "\x2d", + NULL, + "\x8d" "hode Island" "\x2f", + "\x98" "outh " "\x89" "Carolina" "\x30" "\x87" "Dakota" "\x31", + "\x90" "e" "\x88" "nnessee" "\x32" "\x84" "xas" "\x33", + "\x85" "tah" "\x34", + "\x88" "ermont" "\x37" "\x94" "irgin" "\x89" " Islands" "\x36" "\x83" "ia" "\x35", + "\x8b" "ashington" "\x38" "\x8e" "est Virginia" "\x3a" "\x8a" "isconsin" "\x39" + "\x88" "yoming" "\x3b" + }; + +#if 0 // DEBUG_NAV_UI + static char const* const progressNames[] = { + "NO_ADDRESS", + "SKIP_TO_SPACE", + "HOUSE_NUMBER", + "NUMBER_TRAILING_SPACE", + "ADDRESS_LINE", + "STATE_NAME", + "SECOND_HALF", + "ZIP_CODE", + "PLUS_4", + "FIND_STREET" + }; +#endif + // strategy: US only support at first + // look for a 1 - 5 digit number for a street number (no support for 'One Microsoft Way') + // ignore if preceded by '#', Suite, Ste, Rm + // look for two or more words (up to 5? North Frank Lloyd Wright Blvd) + // note: "The Circle at North Hills St." has six words, and a lower 'at' -- allow at, by, of, in, the, and, ... ? + // if a word starts with a lowercase letter, no match + // allow: , . - # / (for 1/2) ' " + // don't look for street name type yet + // look for one or two delimiters to represent possible 2nd addr line and city name + // look for either full state name, or state two letters, and/or zip code (5 or 9 digits) + // now look for street suffix, either in full or abbreviated form, with optional 's' if there's an asterisk + + s->mCurrentStart = chars; + s->mEnd = chars + length; + int candIndex = 0; + bool mustBeAllUpper = false; + bool secondHalf = false; + chars -= 1; + UChar ch = s->mCurrent; + while (++chars <= s->mEnd) { + UChar prior = ch; + ch = chars < s->mEnd ? *chars : ' '; + switch (s->mProgress) { + case NO_ADDRESS: + if (WTF::isASCIIDigit(ch) == false) { + if (ch != 'O') // letter 'O', not zero + continue; + if (s->mEnd - chars < 3) + continue; + prior = *++chars; + ch = *++chars; + if ((prior != 'n' || ch != 'e') && (prior != 'N' || ch != 'E')) + continue; + if (isUnicodeSpace(*++chars) == false) + continue; + s->mProgress = ADDRESS_LINE; + s->mStartResult = chars - 3 - s->mCurrentStart; + continue; + } + if (isUnicodeSpace(prior) == false) { + s->mProgress = SKIP_TO_SPACE; + continue; + } + s->mNumberCount = 1; + s->mProgress = HOUSE_NUMBER; + s->mStartResult = chars - s->mCurrentStart; + continue; + case SKIP_TO_SPACE: + if (isUnicodeSpace(ch) == false) + continue; + break; + case HOUSE_NUMBER: + if (WTF::isASCIIDigit(ch)) { + if (++s->mNumberCount >= 6) + s->mProgress = SKIP_TO_SPACE; + continue; + } + if (WTF::isASCIIUpper(ch)) { // allow one letter after house number, e.g. 12A SKOLFIELD PL, HARPSWELL, ME 04079 + if (WTF::isASCIIDigit(prior) == false) + s->mProgress = SKIP_TO_SPACE; + continue; + } + if (ch == '-') { + if (s->mNumberCount > 0) { // permit 21-23 ELM ST + ++s->mNumberCount; + continue; + } + } + s->mNumberCount = 0; + s->mProgress = NUMBER_TRAILING_SPACE; + case NUMBER_TRAILING_SPACE: + if (isUnicodeSpace(ch)) + continue; + if (0 && WTF::isASCIIDigit(ch)) { + s->mNumberCount = 1; + s->mProgress = HOUSE_NUMBER; + s->mStartResult = chars - s->mCurrentStart; + continue; + } + if (WTF::isASCIIDigit(ch) == false && WTF::isASCIIUpper(ch) == false) + break; + s->mProgress = ADDRESS_LINE; + case ADDRESS_LINE: + if (WTF::isASCIIAlpha(ch) || ch == '\'' || ch == '-' || ch == '&' || ch == '(' || ch == ')') { + if (++s->mLetterCount > 1) { + s->mNumberWords &= ~(1 << s->mWordCount); + continue; + } + if (s->mNumberCount >= 5) + break; +// FIXME: the test below was added to give up on a non-address, but it +// incorrectly discards addresses where the house number is in one node +// and the street name is in the next; I don't recall what the failing case +// is that suggested this fix. +// if (s->mWordCount == 0 && s->mContinuationNode) +// return FOUND_NONE; + s->mBases[s->mWordCount] = baseChars; + s->mWords[s->mWordCount] = chars - s->mNumberCount; + s->mStarts[s->mWordCount] = s->mCurrentStart; + if (WTF::isASCIILower(ch) && s->mNumberCount == 0) + s->mFirstLower = chars; + s->mNumberCount = 0; + if (WTF::isASCIILower(ch) || (WTF::isASCIIAlpha(ch) == false && ch != '-')) + s->mNumberWords &= ~(1 << s->mWordCount); + s->mUnparsed = true; + continue; + } else if (s->mLetterCount >= MAX_PLACE_NAME_LENGTH) { + break; + } else if (s->mFirstLower != NULL) { + size_t length = chars - s->mFirstLower; + if (length > 3) + break; + if (length == 3 && strCharCmp("and" "the", s->mFirstLower, 3, 2) == false) + break; + if (length == 2 && strCharCmp("at" "by" "el" "in" "of", s->mFirstLower, 2, 5) == false) + break; + goto resetWord; + } + if (ch == ',' || ch == '*') { // delimits lines + // asterisk as delimiter: http://www.sa.sc.edu/wellness/members.html + if (++s->mLineCount > 5) + break; + goto lookForState; + } + if (isUnicodeSpace(ch) || prior == '-') { + lookForState: + if (s->mUnparsed == false) + continue; + const UChar* candidate = s->mWords[s->mWordCount]; + UChar firstLetter = candidate[0]; + if (WTF::isASCIIUpper(firstLetter) == false && WTF::isASCIIDigit(firstLetter) == false) + goto resetWord; + s->mWordCount++; + if ((s->mWordCount == 2 && s->mNumberWords == 3 && WTF::isASCIIDigit(s->mWords[1][1])) || // choose second of 8888 333 Main + (s->mWordCount >= sizeof(s->mWords) / sizeof(s->mWords[0]) - 1)) { // subtract 1 since state names may have two parts + // search for simple number already stored since first potential house # didn't pan out + if (s->mNumberWords == 0) + break; + int shift = 0; + while ((s->mNumberWords & (1 << shift)) == 0) + shift++; + s->mNumberWords >>= ++shift; + if (s->mBases[0] != s->mBases[shift]) // if we're past the original node, bail + break; + memmove(s->mBases, &s->mBases[shift], (sizeof(s->mBases) / sizeof(s->mBases[0]) - shift) * sizeof(s->mBases[0])); + memmove(s->mWords, &s->mWords[shift], (sizeof(s->mWords) / sizeof(s->mWords[0]) - shift) * sizeof(s->mWords[0])); + memmove(s->mStarts, &s->mStarts[shift], (sizeof(s->mStarts) / sizeof(s->mStarts[0]) - shift) * sizeof(s->mStarts[0])); + s->mStartResult = s->mWords[0] - s->mStarts[0]; + s->mWordCount -= shift; + // FIXME: need to adjust lineCount to account for discarded delimiters + } + if (s->mWordCount < 4) + goto resetWord; + firstLetter -= 'A'; + if (firstLetter > 'W' - 'A') + goto resetWord; + UChar secondLetter = candidate[1]; + if (prior == '-') + s->mLetterCount--; // trim trailing dashes, to accept CA-94043 + if (s->mLetterCount == 2) { + secondLetter -= 'A'; + if (secondLetter > 'Z' - 'A') + goto resetWord; + if ((stateTwoLetter[firstLetter] & 1 << secondLetter) != 0) { + // special case to ignore 'et al' + if (strCharCmp("ET", s->mWords[s->mWordCount - 2], 2, 1) == false) { + s->mStateWord = s->mWordCount - 1; + s->mZipHint = firstIndex[firstLetter] + + bitcount(stateTwoLetter[firstLetter] & ((1 << secondLetter) - 1)); + goto foundStateName; + } + } + goto resetWord; + } + s->mStates = longStateNames[firstLetter]; + if (s->mStates == NULL) + goto resetWord; + mustBeAllUpper = false; + s->mProgress = STATE_NAME; + unsigned char section = s->mStates[0]; + ASSERT(section > 0x80); + s->mSectionLength = section & 0x7f; + candIndex = 1; + secondHalf = false; + s->mStateWord = s->mWordCount - 1; + goto stateName; + } + if (WTF::isASCIIDigit(ch)) { + if (s->mLetterCount == 0) { + if (++s->mNumberCount > 1) + continue; + if (s->mWordCount == 0 && s->mContinuationNode) + return FOUND_NONE; + s->mBases[s->mWordCount] = baseChars; + s->mWords[s->mWordCount] = chars; + s->mStarts[s->mWordCount] = s->mCurrentStart; + s->mNumberWords |= 1 << s->mWordCount; + s->mUnparsed = true; + } + continue; + } + if (ch == '.') { // optionally can follow letters + if (s->mLetterCount == 0) + break; + if (s->mNumberCount > 0) + break; + continue; + } + if (ch == '/') // between numbers (1/2) between words (12 Main / Ste 4d) + goto resetWord; + if (ch == '#') // can precede numbers, allow it to appear randomly + goto resetWord; + if (ch == '"') // sometimes parts of addresses are quoted (FIXME: cite an example here) + continue; + break; + case SECOND_HALF: + if (WTF::isASCIIAlpha(ch)) { + if (s->mLetterCount == 0) { + s->mBases[s->mWordCount] = baseChars; + s->mWords[s->mWordCount] = chars; + s->mStarts[s->mWordCount] = s->mCurrentStart; + s->mWordCount++; + } + s->mLetterCount++; + continue; + } + if (WTF::isASCIIDigit(ch) == false) { + if (s->mLetterCount > 0) { + s->mProgress = STATE_NAME; + candIndex = 0; + secondHalf = true; + goto stateName; + } + continue; + } + s->mProgress = ADDRESS_LINE; + goto resetState; + case STATE_NAME: + stateName: + // pick up length of first section + do { + int stateIndex = 1; + int skip = 0; + int prefix = 0; + bool subStr = false; + do { + unsigned char match = s->mStates[stateIndex]; + if (match >= 0x80) { + if (stateIndex == s->mSectionLength) + break; + subStr = true; + // if (skip > 0) + // goto foundStateName; + prefix = candIndex; + skip = match & 0x7f; + match = s->mStates[++stateIndex]; + } + UChar candChar = s->mWords[s->mWordCount - 1][candIndex]; + if (mustBeAllUpper && WTF::isASCIILower(candChar)) + goto skipToNext; + if (match != candChar) { + if (match != WTF::toASCIILower(candChar)) { + skipToNext: + if (subStr == false) + break; + if (stateIndex == s->mSectionLength) { + if (secondHalf) { + s->mProgress = ADDRESS_LINE; + goto resetState; + } + break; + } + stateIndex += skip; + skip = 0; + candIndex = prefix; + continue; // try next substring + } + mustBeAllUpper = true; + } + int nextindex = stateIndex + 1; + if (++candIndex >= s->mLetterCount && s->mStates[nextindex] == ' ') { + s->mProgress = SECOND_HALF; + s->mStates += nextindex; + s->mSectionLength -= nextindex; + goto resetWord; + } + if (nextindex + 1 == s->mSectionLength || skip == 2) { + s->mZipHint = s->mStates[nextindex] - 1; + goto foundStateName; + } + stateIndex += 1; + skip -= 1; + } while (true); + s->mStates += s->mSectionLength; + ASSERT(s->mStates[0] == 0 || (unsigned) s->mStates[0] > 0x80); + s->mSectionLength = s->mStates[0] & 0x7f; + candIndex = 1; + subStr = false; + } while (s->mSectionLength != 0); + s->mProgress = ADDRESS_LINE; + goto resetState; + foundStateName: + s->mEndResult = chars - s->mCurrentStart; + s->mEndWord = s->mWordCount - 1; + s->mProgress = ZIP_CODE; + // a couple of delimiters is an indication that the state name is good + // or, a non-space / non-alpha-digit is also good + s->mZipDelimiter = s->mLineCount > 2 || isUnicodeSpace(ch) == false; + if (WTF::isASCIIDigit(ch)) + s->mZipStart = chars; + goto resetState; + case ZIP_CODE: + if (WTF::isASCIIDigit(ch)) { + int count = ++s->mNumberCount; + if (count == 1) { + if (WTF::isASCIIDigit(prior)) + ++s->mNumberCount; + else + s->mZipStart = chars; + } + if (count <= 9) + continue; + } else if (isUnicodeSpace(ch)) { + if (s->mNumberCount == 0) { + s->mZipDelimiter = true; // two spaces delimit state name + continue; + } + } else if (ch == '-') { + if (s->mNumberCount == 5 && validZip(s->mZipHint, s->mZipStart)) { + s->mNumberCount = 0; + s->mProgress = PLUS_4; + continue; + } + if (s->mNumberCount == 0) + s->mZipDelimiter = true; + } else if (WTF::isASCIIAlpha(ch) == false) + s->mZipDelimiter = true; + if (s->mNumberCount == 5 || s->mNumberCount == 9) { + if (validZip(s->mZipHint, s->mZipStart) == false) + goto noZipMatch; + s->mEndResult = chars - s->mCurrentStart; + s->mEndWord = s->mWordCount - 1; + } else if (s->mZipDelimiter == false) { + noZipMatch: + --chars; + s->mProgress = ADDRESS_LINE; + continue; + } + s->mProgress = FIND_STREET; + goto findStreet; + case PLUS_4: + if (WTF::isASCIIDigit(ch)) { + if (++s->mNumberCount <= 4) + continue; + } + if (isUnicodeSpace(ch)) { + if (s->mNumberCount == 0) + continue; + } + if (s->mNumberCount == 4) { + if (WTF::isASCIIAlpha(ch) == false) { + s->mEndResult = chars - s->mCurrentStart; + s->mEndWord = s->mWordCount - 1; + } + } else if (s->mNumberCount != 0) + break; + s->mProgress = FIND_STREET; + case FIND_STREET: + findStreet: // minus two below skips city before state + for (int wordsIndex = s->mStateWord - 2; wordsIndex >= 0; --wordsIndex) { + const UChar* test = s->mWords[wordsIndex]; + UChar letter = test[0]; + letter -= 'A'; + if (letter > 'X' - 'A') + continue; + const char* names = longStreetNames[letter]; + if (names == NULL) + continue; + int offset; + while ((offset = *names++) != 0) { + int testIndex = 1; + bool abbr = false; + for (int idx = 0; idx < offset; idx++) { + char nameLetter = names[idx]; + char testUpper = WTF::toASCIIUpper(test[testIndex]); + if (nameLetter == '*') { + if (testUpper == 'S') + testIndex++; + break; + } + bool fullOnly = WTF::isASCIILower(nameLetter); + nameLetter = WTF::toASCIIUpper(nameLetter); + if (testUpper == nameLetter) { + if (abbr && fullOnly) + goto nextTest; + testIndex++; + continue; + } + if (fullOnly == false) + goto nextTest; + abbr = true; + } + letter = test[testIndex]; + if (WTF::isASCIIAlpha(letter) == false && WTF::isASCIIDigit(letter) == false) { + if (s->mNumberWords != 0) { + int shift = 0; + int wordReduction = -1; + do { + while ((s->mNumberWords & (1 << shift)) == 0) + shift++; + if (shift > wordsIndex) + break; + wordReduction = shift; + } while (s->mNumberWords >> ++shift != 0); + if (wordReduction >= 0) { + if (s->mContinuationNode) + return FOUND_NONE; + s->mStartResult = s->mWords[wordReduction] - s->mStarts[wordReduction]; + } + } + return FOUND_COMPLETE; + } + nextTest: + names += offset; + } + } + if (s->mNumberWords != 0) { + unsigned shift = 0; + while ((s->mNumberWords & (1 << shift)) == 0) + shift++; + s->mNumberWords >>= ++shift; + if (s->mBases[0] != s->mBases[shift]) + return FOUND_NONE; + memmove(s->mBases, &s->mBases[shift], (sizeof(s->mBases) / sizeof(s->mBases[0]) - shift) * sizeof(s->mBases[0])); + memmove(s->mWords, &s->mWords[shift], (sizeof(s->mWords) / sizeof(s->mWords[0]) - shift) * sizeof(s->mWords[0])); + memmove(s->mStarts, &s->mStarts[shift], (sizeof(s->mStarts) / sizeof(s->mStarts[0]) - shift) * sizeof(s->mStarts[0])); + s->mStartResult = s->mWords[0] - s->mStarts[0]; + s->mWordCount -= shift; + s->mProgress = ADDRESS_LINE; + --chars; + continue; + } + break; + } + if (s->mContinuationNode) + return FOUND_NONE; + s->mProgress = NO_ADDRESS; + s->mWordCount = s->mLineCount = 0; + s->mNumberWords = 0; + resetState: + s->mStates = NULL; + resetWord: + s->mNumberCount = s->mLetterCount = 0; + s->mFirstLower = NULL; + s->mUnparsed = false; + } + s->mCurrent = ch; + return s->mProgress == NO_ADDRESS ? FOUND_NONE : FOUND_PARTIAL; +} + +// Recogize common email patterns only. Currently has lots of state, walks text forwards and backwards -- will be +// a real challenge to adapt to walk text across multiple nodes, I imagine +// FIXME: it's too hard for the caller to call these incrementally -- it's probably best for this to +// either walk the node tree directly or make a callout to get the next or previous node, if there is one +// walking directly will avoid adding logic in caller to track the multiple partial or full nodes that compose this +// text pattern. +CacheBuilder::FoundState CacheBuilder::FindPartialEMail(const UChar* chars, unsigned length, + FindState* s) +{ + // the following tables were generated by tests/browser/focusNavigation/BrowserDebug.cpp + // hand-edit at your own risk + static const int domainTwoLetter[] = { + 0x02df797c, // a followed by: [cdefgilmnoqrstuwxz] + 0x036e73fb, // b followed by: [abdefghijmnorstvwyz] + 0x03b67ded, // c followed by: [acdfghiklmnorsuvxyz] + 0x02005610, // d followed by: [ejkmoz] + 0x001e00d4, // e followed by: [ceghrstu] + 0x00025700, // f followed by: [ijkmor] + 0x015fb9fb, // g followed by: [abdefghilmnpqrstuwy] + 0x001a3400, // h followed by: [kmnrtu] + 0x000f7818, // i followed by: [delmnoqrst] + 0x0000d010, // j followed by: [emop] + 0x0342b1d0, // k followed by: [eghimnprwyz] + 0x013e0507, // l followed by: [abcikrstuvy] + 0x03fffccd, // m followed by: [acdghklmnopqrstuvwxyz] + 0x0212c975, // n followed by: [acefgilopruz] + 0x00001000, // o followed by: [m] + 0x014e3cf1, // p followed by: [aefghklmnrstwy] + 0x00000001, // q followed by: [a] + 0x00504010, // r followed by: [eouw] + 0x032a7fdf, // s followed by: [abcdeghijklmnortvyz] + 0x026afeec, // t followed by: [cdfghjklmnoprtvwz] + 0x03041441, // u followed by: [agkmsyz] + 0x00102155, // v followed by: [aceginu] + 0x00040020, // w followed by: [fs] + 0x00000000, // x + 0x00180010, // y followed by: [etu] + 0x00401001, // z followed by: [amw] + }; + + static char const* const longDomainNames[] = { + "\x03" "ero" "\x03" "rpa", // aero, arpa + "\x02" "iz", // biz + "\x02" "at" "\x02" "om" "\x03" "oop", // cat, com, coop + NULL, // d + "\x02" "du", // edu + NULL, // f + "\x02" "ov", // gov + NULL, // h + "\x03" "nfo" "\x02" "nt", // info, int + "\x03" "obs", // jobs + NULL, // k + NULL, // l + "\x02" "il" "\x03" "obi" "\x05" "useum", // mil, mobi, museum + "\x03" "ame" "\x02" "et", // name, net + "\x02" "rg", // , org + "\x02" "ro", // pro + NULL, // q + NULL, // r + NULL, // s + "\x05" "ravel", // travel + NULL, // u + NULL, // v + NULL, // w + NULL, // x + NULL, // y + NULL, // z + }; + + const UChar* start = chars; + const UChar* end = chars + length; + while (chars < end) { + UChar ch = *chars++; + if (ch != '@') + continue; + const UChar* atLocation = chars - 1; + // search for domain + ch = *chars++ | 0x20; // convert uppercase to lower + if (ch < 'a' || ch > 'z') + continue; + while (chars < end) { + ch = *chars++; + if (IsDomainChar(ch) == false) + goto nextAt; + if (ch != '.') + continue; + UChar firstLetter = *chars++ | 0x20; // first letter of the domain + if (chars >= end) + return FOUND_NONE; // only one letter; must be at least two + firstLetter -= 'a'; + if (firstLetter > 'z' - 'a') + continue; // non-letter followed '.' + int secondLetterMask = domainTwoLetter[firstLetter]; + ch = *chars | 0x20; // second letter of the domain + ch -= 'a'; + if (ch >= 'z' - 'a') + continue; + bool secondMatch = (secondLetterMask & 1 << ch) != 0; + const char* wordMatch = longDomainNames[firstLetter]; + int wordIndex = 0; + while (wordMatch != NULL) { + int len = *wordMatch++; + char match; + do { + match = wordMatch[wordIndex]; + if (match < 0x20) + goto foundDomainStart; + if (chars[wordIndex] != match) + break; + wordIndex++; + } while (true); + wordMatch += len; + if (*wordMatch == '\0') + break; + wordIndex = 0; + } + if (secondMatch) { + wordIndex = 1; + foundDomainStart: + chars += wordIndex; + if (chars < end) { + ch = *chars; + if (ch != '.') { + if (IsDomainChar(ch)) + goto nextDot; + } else if (chars + 1 < end && IsDomainChar(chars[1])) + goto nextDot; + } + // found domain. Search backwards from '@' for beginning of email address + s->mEndResult = chars - start; + chars = atLocation; + if (chars <= start) + goto nextAt; + ch = *--chars; + if (ch == '.') + goto nextAt; // mailbox can't end in period + do { + if (IsMailboxChar(ch) == false) { + chars++; + break; + } + if (chars == start) + break; + ch = *--chars; + } while (true); + UChar firstChar = *chars; + if (firstChar == '.' || firstChar == '@') // mailbox can't start with period or be empty + goto nextAt; + s->mStartResult = chars - start; + return FOUND_COMPLETE; + } + nextDot: + ; + } +nextAt: + chars = atLocation + 1; + } + return FOUND_NONE; +} + +#define PHONE_PATTERN "(200) /-.\\ 100 -. 0000" // poor man's regex: parens optional, any one of punct, digit smallest allowed + +CacheBuilder::FoundState CacheBuilder::FindPartialNumber(const UChar* chars, unsigned length, + FindState* s) +{ + char* pattern = s->mPattern; + UChar* store = s->mStorePtr; + const UChar* start = chars; + const UChar* end = chars + length; + const UChar* lastDigit = NULL; + do { + bool initialized = s->mInitialized; + while (chars < end) { + if (initialized == false) { + s->mBackTwo = s->mBackOne; + s->mBackOne = s->mCurrent; + } + UChar ch = s->mCurrent = *chars; + do { + char patternChar = *pattern; + switch (patternChar) { + case '2': + if (initialized == false) { + s->mStartResult = chars - start; + initialized = true; + } + case '0': + case '1': + if (ch < patternChar || ch > '9') + goto resetPattern; + *store++ = ch; + pattern++; + lastDigit = chars; + goto nextChar; + case '\0': + if (WTF::isASCIIDigit(ch) == false) { + *store = '\0'; + goto checkMatch; + } + goto resetPattern; + case ' ': + if (ch == patternChar) + goto nextChar; + break; + case '(': + if (ch == patternChar) { + s->mStartResult = chars - start; + initialized = true; + s->mOpenParen = true; + } + goto commonPunctuation; + case ')': + if ((ch == patternChar) ^ s->mOpenParen) + goto resetPattern; + default: + commonPunctuation: + if (ch == patternChar) { + pattern++; + goto nextChar; + } + } + } while (++pattern); // never false + nextChar: + chars++; + } + break; +resetPattern: + if (s->mContinuationNode) + return FOUND_NONE; + FindResetNumber(s); + pattern = s->mPattern; + store = s->mStorePtr; + } while (++chars < end); +checkMatch: + if (WTF::isASCIIDigit(s->mBackOne != '1' ? s->mBackOne : s->mBackTwo)) + return FOUND_NONE; + *store = '\0'; + s->mStorePtr = store; + s->mPattern = pattern; + s->mEndResult = lastDigit - start + 1; + char pState = pattern[0]; + return pState == '\0' ? FOUND_COMPLETE : pState == '(' || (WTF::isASCIIDigit(pState) && WTF::isASCIIDigit(pattern[-1])) ? + FOUND_NONE : FOUND_PARTIAL; +} + +CacheBuilder::FoundState CacheBuilder::FindPhoneNumber(const UChar* chars, unsigned length, + int* start, int* end) +{ + FindState state; + FindReset(&state); + FoundState result = FindPartialNumber(chars, length, &state); + *start = state.mStartResult; + *end = state.mEndResult; + return result; +} + +void CacheBuilder::FindReset(FindState* state) +{ + memset(state, 0, sizeof(FindState)); + state->mCurrent = ' '; + FindResetNumber(state); +} + +void CacheBuilder::FindResetNumber(FindState* state) +{ + state->mOpenParen = false; + state->mPattern = (char*) PHONE_PATTERN; + state->mStorePtr = state->mStore; +} + +IntRect CacheBuilder::getAreaRect(const HTMLAreaElement* area) const +{ + RenderImage* map = m_areaBoundsMap.get(area); + if (!map) + return IntRect(); + if (area->isDefault()) + return map->absoluteBoundingBoxRect(); + return area->getRect(map); +} + +void CacheBuilder::GetGlobalOffset(Node* node, int* x, int * y) +{ + GetGlobalOffset(node->document()->frame(), x, y); +} + +void CacheBuilder::GetGlobalOffset(Frame* frame, int* x, int* y) +{ +// TIMER_PROBE(__FUNCTION__); + ASSERT(x); + ASSERT(y); + *x = 0; + *y = 0; + if (!frame->view()) + return; + Frame* parent; + while ((parent = frame->tree()->parent()) != NULL) { + const WebCore::IntRect& rect = frame->view()->platformWidget()->getBounds(); + *x += rect.x(); + *y += rect.y(); + frame = parent; + } + // TIMER_PROBE_END(); +} + +Frame* CacheBuilder::HasFrame(Node* node) +{ + RenderObject* renderer = node->renderer(); + if (renderer == NULL) + return NULL; + if (renderer->isWidget() == false) + return NULL; + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (widget == NULL) + return NULL; + if (widget->isFrameView() == false) + return NULL; + return static_cast<FrameView*>(widget)->frame(); +} + +bool CacheBuilder::HasOverOrOut(Node* node) +{ + EventTargetNode* target = (EventTargetNode*) node; + return target->getEventListener(eventNames().mouseoverEvent) || + target->getEventListener(eventNames().mouseoutEvent); + +} + +bool CacheBuilder::HasTriggerEvent(Node* node) +{ + EventTargetNode* target = (EventTargetNode*) node; + return target->getEventListener(eventNames().clickEvent) || + target->getEventListener(eventNames().mousedownEvent) || + target->getEventListener(eventNames().mouseupEvent) || + target->getEventListener(eventNames().keydownEvent) || + target->getEventListener(eventNames().keyupEvent); +} + +// #define EMAIL_PATTERN "x@y.d" // where 'x' is letters, numbers, and '-', '.', '_' ; 'y' is 'x' without the underscore, and 'd' is a valid domain +// - 0x2D . 0x2E 0-9 0x30-39 A-Z 0x41-5A _ 0x5F a-z 0x61-7A + +bool CacheBuilder::IsDomainChar(UChar ch) +{ + static const unsigned body[] = {0x03ff6000, 0x07fffffe, 0x07fffffe}; // 0-9 . - A-Z a-z + ch -= 0x20; + if (ch > 'z' - 0x20) + return false; + return (body[ch >> 5] & 1 << (ch & 0x1f)) != 0; +} + +// does not find text to keep it fast +// (this assume text nodes are more rarely moved than other nodes) +Node* CacheBuilder::findByCenter(int x, int y) const +{ + DBG_NAV_LOGD("x=%d y=%d\n", x, y); + Frame* frame = FrameAnd(this); + Node* node = frame->document(); + ASSERT(node != NULL); + int globalOffsetX, globalOffsetY; + GetGlobalOffset(frame, &globalOffsetX, &globalOffsetY); + while ((node = node->traverseNextNode()) != NULL) { + Frame* child = HasFrame(node); + if (child != NULL) { + if (child->document() == NULL) + continue; + CacheBuilder* cacheBuilder = Builder(child); + // if (cacheBuilder->mViewBounds.isEmpty()) + // continue; + Node* result = cacheBuilder->findByCenter(x, y); + if (result != NULL) + return result; + } + if (node->isTextNode()) + continue; + IntRect bounds; + if (node->hasTagName(HTMLNames::areaTag)) { + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); + bounds = getAreaRect(area); + bounds.move(globalOffsetX, globalOffsetY); + } else + bounds = node->getRect(); + if (bounds.isEmpty()) + continue; + bounds.move(globalOffsetX, globalOffsetY); + if (x != bounds.x() + (bounds.width() >> 1)) + continue; + if (y != bounds.y() + (bounds.height() >> 1)) + continue; + if (node->isKeyboardFocusable(NULL)) + return node; + if (node->isMouseFocusable()) + return node; + if (node->isFocusable()) + return node; + if (node->isEventTargetNode() == false) + continue; + if (AnyIsClick(node)) + continue; + if (HasTriggerEvent(node) == false) + continue; + return node; + } + return NULL; +} + +bool CacheBuilder::isFocusableText(NodeWalk* walk, bool more, Node* node, + CachedNodeType* type, String* exported) const +{ + Text* textNode = static_cast<Text*>(node); + StringImpl* string = textNode->string(); + const UChar* baseChars = string->characters(); +// const UChar* originalBase = baseChars; + int length = string->length(); + int index = 0; + while (index < length && isUnicodeSpace(baseChars[index])) + index++; + if (index >= length) + return false; + if (more == false) { + walk->mStart = 0; + walk->mEnd = 0; + walk->mFinalNode = node; + walk->mLastInline = NULL; + } + // starting with this node, search forward for email, phone number, and address + // if any of the three is found, track it so that the remaining can be looked for later + FoundState state = FOUND_NONE; + RenderText* renderer = (RenderText*) node->renderer(); + bool foundBetter = false; + InlineTextBox* baseInline = walk->mLastInline != NULL ? walk->mLastInline : + renderer->firstTextBox(); + if (baseInline == NULL) + return false; + int start = walk->mEnd; + InlineTextBox* saveInline; + int baseStart, firstStart = start; + saveInline = baseInline; + baseStart = start; + for (CachedNodeType checkType = ADDRESS_CACHEDNODETYPE; + checkType <= PHONE_CACHEDNODETYPE; + checkType = (CachedNodeType) (checkType << 1)) + { + if ((checkType & mAllowableTypes) == 0) + continue; + InlineTextBox* inlineTextBox = baseInline; + FindState findState; + FindReset(&findState); + start = baseStart; + if (checkType == ADDRESS_CACHEDNODETYPE) { + findState.mBases[0] = baseChars; + findState.mWords[0] = baseChars + start; + findState.mStarts[0] = baseChars + start; + } + Node* lastPartialNode = NULL; + int lastPartialEnd = -1; + bool lastPartialMore = false; + bool firstPartial = true; + InlineTextBox* lastPartialInline = NULL; + do { + do { + const UChar* chars = baseChars + start; + length = inlineTextBox == NULL ? 0 : + inlineTextBox->end() - start + 1; + bool wasInitialized = findState.mInitialized; + switch (checkType) { + case ADDRESS_CACHEDNODETYPE: + state = FindPartialAddress(baseChars, chars, length, &findState); + break; + case EMAIL_CACHEDNODETYPE: + state = FindPartialEMail(chars, length, &findState); + break; + case PHONE_CACHEDNODETYPE: + state = FindPartialNumber(chars, length, &findState); + break; + default: + ASSERT(0); + } + findState.mInitialized = state != FOUND_NONE; + if (wasInitialized != findState.mInitialized) + firstStart = start; + if (state == FOUND_PARTIAL) { + lastPartialNode = node; + lastPartialEnd = findState.mEndResult + start; + lastPartialMore = firstPartial && + lastPartialEnd < (int) string->length(); + firstPartial = false; + lastPartialInline = inlineTextBox; + findState.mContinuationNode = true; + } else if (state == FOUND_COMPLETE) { + if (foundBetter == false || walk->mStart > findState.mStartResult) { + walk->mStart = findState.mStartResult + firstStart; + if (findState.mEndResult > 0) { + walk->mFinalNode = node; + walk->mEnd = findState.mEndResult + start; + walk->mMore = node == textNode && + walk->mEnd < (int) string->length(); + walk->mLastInline = inlineTextBox; + } else { + walk->mFinalNode = lastPartialNode; + walk->mEnd = lastPartialEnd; + walk->mMore = lastPartialMore; + walk->mLastInline = lastPartialInline; + } + *type = checkType; + if (checkType == PHONE_CACHEDNODETYPE) { + const UChar* store = findState.mStore; + *exported = String(store); + } else { + Node* temp = textNode; + length = 1; + start = walk->mStart; + exported->truncate(0); + do { + Text* tempText = static_cast<Text*>(temp); + StringImpl* string = tempText->string(); + int end = tempText == walk->mFinalNode ? + walk->mEnd : string->length(); + exported->append(String(string->substring( + start, end - start))); + ASSERT(end > start); + length += end - start + 1; + if (temp == walk->mFinalNode) + break; + start = 0; + do { + temp = temp->traverseNextNode(); + ASSERT(temp); + } while (temp->isTextNode() == false); + // add a space in between text nodes to avoid + // words collapsing together + exported->append(" "); + } while (true); + } + foundBetter = true; + } + goto tryNextCheckType; + } else if (findState.mContinuationNode) + break; + if (inlineTextBox == NULL) + break; + inlineTextBox = inlineTextBox->nextTextBox(); + if (inlineTextBox == NULL) + break; + start = inlineTextBox->start(); + if (state == FOUND_PARTIAL && node == textNode) + findState.mContinuationNode = false; + } while (true); + if (state == FOUND_NONE) + break; + // search for next text node, if any + Text* nextNode; + do { + do { + do { + node = node->traverseNextNode(); + if (node == NULL || node->hasTagName(HTMLNames::aTag)) { + if (state == FOUND_PARTIAL && + checkType == ADDRESS_CACHEDNODETYPE && + findState.mProgress == ZIP_CODE && + findState.mNumberCount == 0) { + baseChars = NULL; + inlineTextBox = NULL; + start = 0; + findState.mProgress = FIND_STREET; + goto finalNode; + } + goto tryNextCheckType; + } + } while (node->isTextNode() == false); + nextNode = static_cast<Text*>(node); + renderer = (RenderText*) nextNode->renderer(); + } while (renderer == NULL); + baseInline = renderer->firstTextBox(); + } while (baseInline == NULL); + string = nextNode->string(); + baseChars = string->characters(); + inlineTextBox = baseInline; + start = inlineTextBox->start(); + finalNode: + findState.mEndResult = 0; + } while (true); +tryNextCheckType: + node = textNode; + baseInline = saveInline; + string = textNode->string(); + baseChars = string->characters(); + } + if (foundBetter) { + CachedNodeType temp = *type; + switch (temp) { + case ADDRESS_CACHEDNODETYPE: { + static const char geoString[] = "geo:0,0?q="; + exported->insert(String(geoString), 0); + int index = sizeof(geoString) - 1; + String escapedComma("%2C"); + while ((index = exported->find(',', index)) >= 0) + exported->replace(index, 1, escapedComma); + } break; + case EMAIL_CACHEDNODETYPE: + exported->insert(WebCore::String("mailto:"), 0); + break; + case PHONE_CACHEDNODETYPE: + exported->insert(WebCore::String("tel:"), 0); + break; + default: + break; + } + return true; + } +noTextMatch: + walk->reset(); + return false; +} + +bool CacheBuilder::IsMailboxChar(UChar ch) +{ + static const unsigned body[] = {0x03ff6000, 0x87fffffe, 0x07fffffe}; // 0-9 . - A-Z _ a-z + ch -= 0x20; + if (ch > 'z' - 0x20) + return false; + return (body[ch >> 5] & 1 << (ch & 0x1f)) != 0; +} + +bool CacheBuilder::outOfDate() +{ + Node* kitFocusNode = currentFocus(); + if (mLastKnownFocus != kitFocusNode) { + DBG_NAV_LOGD("%s\n", "mLastKnownFocus != kitFocusNode"); + return true; + } + if (kitFocusNode == NULL) + return false; + IntRect kitBounds = kitFocusNode->getRect(); + bool result = kitBounds != mLastKnownFocusBounds; + if (result == true) + DBG_NAV_LOGD("%s\n", "kitBounds != mLastKnownFocusBounds"); + return result; +} + +void CacheBuilder::setLastFocus(Node* node) +{ + ASSERT(node); + mLastKnownFocus = node; + mLastKnownFocusBounds = node->getRect(); +} + +bool CacheBuilder::setData(CachedFrame* cachedFrame) +{ + Frame* frame = FrameAnd(this); + Document* doc = frame->document(); + if (doc == NULL) + return false; + RenderObject* renderer = doc->renderer(); + if (renderer == NULL) + return false; + RenderLayer* layer = renderer->enclosingLayer(); + if (layer == NULL) + return false; + if (layer->width() == 0 || layer->height() == 0) + return false; + if (!frame->view()) + return false; + int x, y; + GetGlobalOffset(frame, &x, &y); + WebCore::IntRect viewBounds = frame->view()->platformWidget()->getBounds(); + if ((x | y) != 0) + viewBounds.setLocation(WebCore::IntPoint(x, y)); + cachedFrame->setLocalViewBounds(viewBounds); + cachedFrame->setContentsSize(layer->width(), layer->height()); + if (cachedFrame->childCount() == 0) + return true; + CachedFrame* lastCachedFrame = cachedFrame->lastChild(); + cachedFrame = cachedFrame->firstChild(); + do { + CacheBuilder* cacheBuilder = Builder((Frame* )cachedFrame->framePointer()); + cacheBuilder->setData(cachedFrame); + } while (cachedFrame++ != lastCachedFrame); + return true; +} + +bool CacheBuilder::validNode(void* matchFrame, void* matchNode) const +{ + Frame* frame = FrameAnd(this); + if (matchFrame == frame) { + if (matchNode == NULL) + return true; + Node* node = frame->document(); + while (node != NULL) { + if (node == matchNode) { + const IntRect& rect = node->hasTagName(HTMLNames::areaTag) ? + getAreaRect(static_cast<HTMLAreaElement*>(node)) : node->getRect(); + // Consider nodes with empty rects that are not at the origin + // to be valid, since news.google.com has valid nodes like this + if (rect.x() == 0 && rect.y() == 0 && rect.isEmpty()) + return false; + return true; + } + node = node->traverseNextNode(); + } + DBG_NAV_LOGD("frame=%p valid node=%p invalid\n", matchFrame, matchNode); + return false; + } + Frame* child = frame->tree()->firstChild(); + while (child) { + bool result = Builder(child)->validNode(matchFrame, matchNode); + if (result) + return result; + child = child->tree()->nextSibling(); + } +#if DEBUG_NAV_UI + if (frame->tree()->parent() == NULL) + DBG_NAV_LOGD("frame=%p node=%p false\n", matchFrame, matchNode); +#endif + return false; +} + +static int Area(const IntRect& rect) +{ + return rect.width() * rect.height(); +} + +bool CacheBuilder::AddPartRect(IntRect& bounds, int x, int y, + WTF::Vector<IntRect>* result, IntRect* focusBounds) +{ + if (bounds.isEmpty()) + return true; + bounds.move(x, y); + if (bounds.right() <= 0 || bounds.bottom() <= 0) + return true; + IntRect* work = result->begin() - 1; + IntRect* end = result->end(); + while (++work < end) { + if (work->contains(bounds)) + return true; + if (bounds.contains(*work)) { + *work = bounds; + focusBounds->unite(bounds); + return true; + } + if ((bounds.x() != work->x() || bounds.width() != work->width()) && + (bounds.y() != work->y() || bounds.height() != work->height())) + continue; + IntRect test = *work; + test.unite(bounds); + if (Area(test) > Area(*work) + Area(bounds)) + continue; + *work = test; + focusBounds->unite(bounds); + return true; + } + if (result->size() >= MAXIMUM_FOCUS_RING_COUNT) + return false; + result->append(bounds); + if (focusBounds->isEmpty()) + *focusBounds = bounds; + else + focusBounds->unite(bounds); + return true; +} + +bool CacheBuilder::ConstructPartRects(Node* node, const IntRect& bounds, + IntRect* focusBounds, int x, int y, WTF::Vector<IntRect>* result) +{ + WTF::Vector<ClipColumnTracker> clipTracker(1); + ClipColumnTracker* baseTracker = clipTracker.data(); // sentinel + bzero(baseTracker, sizeof(ClipColumnTracker)); + if (node->hasChildNodes() && node->hasTagName(HTMLNames::buttonTag) == false + && node->hasTagName(HTMLNames::selectTag) == false) { + // collect all text rects from first to last child + Node* test = node->firstChild(); + Node* last = NULL; + Node* prior = node; + while ((prior = prior->lastChild()) != NULL) + last = prior; + ASSERT(last != NULL); + bool nodeIsAnchor = node->hasTagName(HTMLNames::aTag); + do { + do { + const ClipColumnTracker* lastClip = &clipTracker.last(); + if (test != lastClip->mLastChild) + break; + clipTracker.removeLast(); + } while (true); + RenderObject* renderer = test->renderer(); + if (renderer == NULL) + continue; + EVisibility vis = renderer->style()->visibility(); + if (vis == HIDDEN) + continue; + if (test->isTextNode()) { + RenderText* renderText = (RenderText*) renderer; + InlineTextBox *textBox = renderText->firstTextBox(); + if (textBox == NULL) + continue; + bool hasClip = renderer->hasOverflowClip(); + size_t clipIndex = clipTracker.size(); + IntRect clipBounds = IntRect(0, 0, INT_MAX, INT_MAX); + if (hasClip || --clipIndex > 0) { + clipBounds = hasClip ? renderer->absoluteBoundingBoxRect() : + clipTracker.at(clipIndex).mBounds; // x, y fixup done by ConstructTextRect + } + if (ConstructTextRect((Text*) test, textBox, 0, INT_MAX, + x, y, focusBounds, clipBounds, result) == false) { + return false; + } + continue; + } + if (test->hasTagName(HTMLNames::imgTag)) { + IntRect bounds = test->getRect(); + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + continue; + } + if (renderer->hasOverflowClip() == false) { + if (nodeIsAnchor && test->hasTagName(HTMLNames::divTag)) { + IntRect bounds = renderer->absoluteBoundingBoxRect(); // x, y fixup done by AddPartRect + int left = bounds.x() + renderer->paddingLeft() + + renderer->borderLeft(); + int top = bounds.y() + renderer->paddingTop() + + renderer->borderTop(); + int right = bounds.right() - renderer->paddingRight() + - renderer->borderRight(); + int bottom = bounds.bottom() - renderer->paddingBottom() + - renderer->borderBottom(); + if (left >= right || top >= bottom) + continue; + bounds = IntRect(left, top, right - left, bottom - top); + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + } + continue; + } + Node* lastChild = test->lastChild(); + if (lastChild == NULL) + continue; + clipTracker.grow(clipTracker.size() + 1); + ClipColumnTracker& clip = clipTracker.last(); + clip.mBounds = renderer->absoluteBoundingBoxRect(); // x, y fixup done by ConstructTextRect + clip.mLastChild = OneAfter(lastChild); + clip.mNode = test; + } while (test != last && (test = test->traverseNextNode()) != NULL); + } + if (result->size() == 0 || focusBounds->width() < MINIMUM_FOCUSABLE_WIDTH + || focusBounds->height() < MINIMUM_FOCUSABLE_HEIGHT) { + if (bounds.width() < MINIMUM_FOCUSABLE_WIDTH) + return false; + if (bounds.height() < MINIMUM_FOCUSABLE_HEIGHT) + return false; + result->append(bounds); + *focusBounds = bounds; + } + return true; +} + +static inline bool isNotSpace(UChar c) +{ + return c <= 0xA0 ? isUnicodeSpace(c) == false : + WTF::Unicode::direction(c) != WTF::Unicode::WhiteSpaceNeutral; +} + +bool CacheBuilder::ConstructTextRect(Text* textNode, + InlineTextBox* textBox, int start, int relEnd, int x, int y, + IntRect* focusBounds, const IntRect& clipBounds, WTF::Vector<IntRect>* result) +{ + RenderText* renderText = (RenderText*) textNode->renderer(); + EVisibility vis = renderText->style()->visibility(); + StringImpl* string = textNode->string(); + const UChar* chars = string->characters(); + int renderX, renderY; + renderText->absolutePosition(renderX, renderY); + do { + int textBoxStart = textBox->start(); + int textBoxEnd = textBoxStart + textBox->len(); + if (textBoxEnd <= start) + continue; + if (textBoxEnd > relEnd) + textBoxEnd = relEnd; + IntRect bounds = textBox->selectionRect(renderX, renderY, + start, textBoxEnd); + bounds.intersect(clipBounds); + if (bounds.isEmpty()) + continue; + bool drawable = false; + for (int index = start; index < textBoxEnd; index++) + if ((drawable |= isNotSpace(chars[index])) != false) + break; + if (drawable && vis != HIDDEN) { + if (AddPartRect(bounds, x, y, result, focusBounds) == false) + return false; + } + if (textBoxEnd == relEnd) + break; + } while ((textBox = textBox->nextTextBox()) != NULL); + return true; +} + +bool CacheBuilder::ConstructTextRects(Text* node, int start, + Text* last, int end, int x, int y, IntRect* focusBounds, + const IntRect& clipBounds, WTF::Vector<IntRect>* result) +{ + result->clear(); + *focusBounds = IntRect(0, 0, 0, 0); + do { + RenderText* renderText = (RenderText*) node->renderer(); + int relEnd = node == last ? end : renderText->textLength(); + InlineTextBox *textBox = renderText->firstTextBox(); + if (textBox != NULL) { + do { + if ((int) textBox->end() >= start) + break; + } while ((textBox = textBox->nextTextBox()) != NULL); + if (ConstructTextRect(node, textBox, start, relEnd, + x, y, focusBounds, clipBounds, result) == false) + return false; + } + start = 0; + do { + if (node == last) + return true; + node = (Text*) node->traverseNextNode(); + ASSERT(node != NULL); + } while (node->isTextNode() == false || node->renderer() == NULL); + } while (true); +} + +} diff --git a/WebKit/android/nav/CacheBuilder.h b/WebKit/android/nav/CacheBuilder.h new file mode 100644 index 0000000..07040e2 --- /dev/null +++ b/WebKit/android/nav/CacheBuilder.h @@ -0,0 +1,272 @@ +/* + * Copyright 2006, 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef CacheBuilder_H +#define CacheBuilder_H + +#include "CachedDebug.h" +#include "CachedNodeType.h" +#include "IntRect.h" +#include "PlatformString.h" +#include "TextDirection.h" +#include "wtf/HashMap.h" +#include "wtf/Vector.h" + +#define NAVIGATION_MAX_PHONE_LENGTH 14 + +using namespace WebCore; + +namespace WebCore { + +class Document; +class Frame; +class HTMLAreaElement; +class InlineTextBox; +class Node; +class PlatformGraphicsContext; +class RenderFlow; +class RenderImage; +class RenderObject; +class RenderLayer; +class Text; + +} + +namespace android { + +class CachedFrame; +class CachedNode; +class CachedRoot; + +class CacheBuilder { +public: + enum Direction { + UNINITIALIZED = -1, + LEFT, + RIGHT, + UP, + DOWN, + DIRECTION_COUNT, + UP_DOWN = UP & DOWN, // mask and result + RIGHT_DOWN = RIGHT & DOWN, // mask and result + }; + enum FoundState { + FOUND_NONE, + FOUND_PARTIAL, + FOUND_COMPLETE + }; + CacheBuilder(); + void allowAllTextDetection() { mAllowableTypes = ALL_CACHEDNODETYPES; } + void buildCache(CachedRoot* root); + static bool ConstructPartRects(Node* node, const IntRect& bounds, + IntRect* focusBounds, int x, int y, WTF::Vector<IntRect>* result); + Node* currentFocus() const; + void disallowAddressDetection() { mAllowableTypes = (CachedNodeType) ( + mAllowableTypes & ~ADDRESS_CACHEDNODETYPE); } + void disallowEmailDetection() { mAllowableTypes = (CachedNodeType) ( + mAllowableTypes & ~EMAIL_CACHEDNODETYPE); } + void disallowPhoneDetection() { mAllowableTypes = (CachedNodeType) ( + mAllowableTypes & ~PHONE_CACHEDNODETYPE); } + static FoundState FindAddress(const UChar* , unsigned length, int* start, int* end); + Node* findByCenter(int x, int y) const; + static void GetGlobalOffset(Frame* , int* x, int * y); + static void GetGlobalOffset(Node* , int* x, int * y); + bool outOfDate(); + void setLastFocus(Node* ); + bool validNode(void* framePtr, void* nodePtr) const; +private: + enum AddressProgress { + NO_ADDRESS, + SKIP_TO_SPACE, + HOUSE_NUMBER, + NUMBER_TRAILING_SPACE, + ADDRESS_LINE, + STATE_NAME, + SECOND_HALF, + ZIP_CODE, + PLUS_4, + FIND_STREET + }; + struct NodeWalk { + NodeWalk() { reset(); } + int mStart; + int mEnd; + Node* mFinalNode; + InlineTextBox* mLastInline; + bool mMore; + void reset() { mMore = false; } + }; + struct BoundsPart { + IntRect mRect; + int mStart; + int mEnd; + }; + struct Bounds { + typedef bool (*FindText)(BoundsPart* result, InlineTextBox* , const String& match); + IntRect mNodeBounds; + BoundsPart mPart; + WTF::Vector<BoundsPart> mParts; + char mStore[NAVIGATION_MAX_PHONE_LENGTH + 1]; + CachedNodeType mStoreType; + int mPartIndex; + Node* mNode; + Node* mFinalNode; + void reset() { mNode = NULL; } + }; + struct FindState { + int mStartResult; + int mEndResult; + const UChar* mCurrentStart; + const UChar* mEnd; + AddressProgress mProgress; + int mNumberCount; + int mLetterCount; + unsigned mWordCount; + int mLineCount; + const UChar* mFirstLower; + const UChar* mZipStart; + const UChar* mBases[16]; // FIXME: random guess, maybe too small, maybe too big + const UChar* mWords[16]; + const UChar* mStarts[16]; // text is not necessarily contiguous + const char* mStates; + int mEndWord; + int mStateWord; + int mZipHint; + int mSectionLength; + unsigned mNumberWords; // must contain as many bits as mWords contains elements + char* mPattern; + UChar mStore[NAVIGATION_MAX_PHONE_LENGTH + 1]; + UChar* mStorePtr; + UChar mBackOne; + UChar mBackTwo; + UChar mCurrent; + bool mUnparsed; + bool mZipDelimiter; + bool mOpenParen; + bool mInitialized; + bool mContinuationNode; + }; + struct ClipColumnTracker { + IntRect mBounds; + Node* mLastChild; + Node* mNode; + WTF::Vector<IntRect>* mColumns; + int mColumnGap; + TextDirection mDirection; + bool mHasClip; + }; + struct TabIndexTracker { + int mTabIndex; + Node* mLastChild; + }; + struct Tracker { + int mCachedNodeIndex; + int mTabIndex; + Node* mLastChild; + Node* mParentLastChild; + bool mSomeParentTakesFocus; + }; + void adjustForColumns(const ClipColumnTracker& track, + CachedNode* node, IntRect* bounds); + static bool AddPartRect(IntRect& bounds, int x, int y, + WTF::Vector<IntRect>* result, IntRect* focusBounds); + static bool AnyIsClick(Node* node); + static bool AnyChildIsClick(Node* node); + void BuildFrame(Frame* root, Frame* frame, + CachedRoot* cachedRoot, CachedFrame* cachedFrame); + bool CleanUpContainedNodes(CachedFrame* cachedFrame, + const Tracker* last, int lastChildIndex); + static bool ConstructTextRect(Text* textNode, + InlineTextBox* textBox, int start, int relEnd, int x, int y, + IntRect* focusBounds, const IntRect& clip, WTF::Vector<IntRect>* result); + static bool ConstructTextRects(Text* node, int start, + Text* last, int end, int x, int y, IntRect* focusBounds, + const IntRect& clip, WTF::Vector<IntRect>* result); + static FoundState FindPartialAddress(const UChar* , const UChar* , unsigned length, FindState* ); + static FoundState FindPartialEMail(const UChar* , unsigned length, FindState* ); + static FoundState FindPartialNumber(const UChar* , unsigned length, FindState* ); + static FoundState FindPhoneNumber(const UChar* chars, unsigned length, int* start, int* end); + static void FindReset(FindState* ); + static void FindResetNumber(FindState* ); + static Frame* FrameAnd(CacheBuilder* focusNav); + static Frame* FrameAnd(const CacheBuilder* focusNav); + static CacheBuilder* Builder(Frame* ); + IntRect getAreaRect(const HTMLAreaElement* area) const; + static Frame* HasFrame(Node* ); + static bool HasOverOrOut(Node* ); + static bool HasTriggerEvent(Node* ); + static bool IsDomainChar(UChar ch); + bool isFocusableText(NodeWalk* , bool oldMore, Node* , CachedNodeType* type, + String* exported) const; //returns true if it is focusable + static bool IsMailboxChar(UChar ch); + static bool IsRealNode(Frame* , Node* ); + int overlap(int left, int right); // returns distance scale factor as 16.16 scalar + bool setData(CachedFrame* ); + Node* tryFocus(Direction direction); + Node* trySegment(Direction direction, int mainStart, int mainEnd); + Node* mLastKnownFocus; + IntRect mLastKnownFocusBounds; + CachedNodeType mAllowableTypes; + WTF::HashMap<const HTMLAreaElement* , RenderImage* > m_areaBoundsMap; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + void frameName(char*& namePtr, const char* max) const; + void init(char* buffer, size_t size); + static int ParentIndex(Node* node, int count, Node* parent); + void print() { frames(); } + void print(const char* name); + void wideString(const String& str); +private: + void attr(const AtomicString& name, const AtomicString& value); + void comma(const char* str); + int flowBoxes(RenderFlow* flow, int ifIndex, int indent); + void flush(); + Frame* frameAnd() const; + void frames(); + void groups(); + bool isFocusable(Node* node); + void localName(Node* node); + void newLine(int indent = 0); + void print(const char* name, unsigned len); + void renderTree(RenderObject* , int indent, Node* , int count); + void setIndent(int ); + void uChar(const UChar* name, unsigned len, bool hex); + void validateFrame(); + void validateStringData(); + void wideString(const UChar* chars, int length, bool hex); + char* mBuffer; + size_t mBufferSize; + int mIndex; + const char* mPrefix; + int mMinPrefix; + } mDebug; +#endif +}; + +} + +#endif diff --git a/WebKit/android/nav/CachedDebug.h b/WebKit/android/nav/CachedDebug.h new file mode 100644 index 0000000..3127112 --- /dev/null +++ b/WebKit/android/nav/CachedDebug.h @@ -0,0 +1,74 @@ +/* + * 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef CachedDebug_H +#define CachedDebug_H + +#ifndef DUMP_NAV_CACHE +#ifdef NDEBUG +#define DUMP_NAV_CACHE 0 +#else +#define DUMP_NAV_CACHE 1 +#endif +#endif + +#ifndef DEBUG_NAV_UI +#ifdef NDEBUG +#define DEBUG_NAV_UI 0 +#else +#define DEBUG_NAV_UI 1 +#endif +#endif + +#if DEBUG_NAV_UI +#define DBG_NAV_LOG(message) LOGD("%s %s", __FUNCTION__, message) +#define DBG_NAV_LOGD(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#define DEBUG_NAV_UI_LOGD(...) LOGD(__VA_ARGS__) +#else +#define DBG_NAV_LOG(message) ((void)0) +#define DBG_NAV_LOGD(format, ...) ((void)0) +#define DBG_NAV_LOGD_THROTTLE(format, ...) ((void)0) +#define DEBUG_NAV_UI_LOGD(...) ((void)0) +#endif + +#if DUMP_NAV_CACHE != 0 && !defined DUMP_NAV_CACHE_USING_PRINTF && defined NDEBUG +#define DUMP_NAV_CACHE_USING_PRINTF +#endif + +#if DUMP_NAV_CACHE +#ifdef DUMP_NAV_CACHE_USING_PRINTF +#include <stdio.h> +extern FILE* gNavCacheLogFile; +#define NAV_CACHE_LOG_FILE "/data/data/com.android.browser/navlog" +#define DUMP_NAV_LOGD(...) do { if (gNavCacheLogFile) \ + fprintf(gNavCacheLogFile, __VA_ARGS__); else LOGD(__VA_ARGS__); } while (false) +#else +#define DUMP_NAV_LOGD(...) LOGD(__VA_ARGS__) +#endif +#else +#define DUMP_NAV_LOGD(...) ((void)0) +#endif + +#endif diff --git a/WebKit/android/nav/CachedFrame.cpp b/WebKit/android/nav/CachedFrame.cpp new file mode 100644 index 0000000..4db9e40 --- /dev/null +++ b/WebKit/android/nav/CachedFrame.cpp @@ -0,0 +1,1318 @@ +/* + * 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 APPLE COMPUTER, INC. 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 { + +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::checkVisited(const CachedNode* node, Direction direction) const +{ + return history()->checkVisited(node, direction); +} + +void CachedFrame::clearFocus() +{ + if (mFocus < 0) + return; + CachedNode& focus = mCachedNodes[mFocus]; + focus.clearFocus(this); + mFocus = -1; +} + +// the thing that sucks is that when you're on a link, you want to navigate next door to a link just like this one, but can't make it +// so with all my other sucky compares, maybe there needs to be one that prefers links that are aligned with the current focus... + +// 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 CachedNode* focus) const +{ + if (testData.mNode->tabIndex() != bestData.mNode->tabIndex()) { + if (testData.mNode->tabIndex() < bestData.mNode->tabIndex() + || (focus && focus->tabIndex() < bestData.mNode->tabIndex())) { + testData.mNode->setCondition(CachedNode::HIGHER_TAB_INDEX); + return REJECT_TEST; + } + return TEST_IS_BEST; + } +// start here; + // if the test minor axis line intersects the line segment between focus 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_FOCUS); + return REJECT_TEST; + } + return TEST_IS_BEST; + } + if (testData.mInNav) { + if (bestData.mMajorDelta < testData.mMajorDelta) { + testData.mNode->setCondition(CachedNode::CLOSER_IN_FOCUS); + 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; +// SkFixed focusMultiplier = SK_Fixed1; +// if (focus != NULL) { +// if (testData.mMajorDelta < bestData.mMajorDelta) { +// // use bestData.mMajorDelta, +// } else if (bestData.mMajorDelta < testData.mMajorDelta) { +// +// } + 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.mFocusChild != bestData.mFocusChild) { + if (bestData.mFocusChild) { + testData.mNode->setCondition(CachedNode::IN_FOCUS_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::currentFocus(const CachedFrame** framePtr) const +{ + if (framePtr) + *framePtr = this; + if (mFocus < 0) + return NULL; + const CachedNode* result = &mCachedNodes[mFocus]; + const CachedFrame* frame = hasFrame(result); + if (frame != NULL) + return frame->currentFocus(framePtr); + (const_cast<CachedNode*>(result))->fixUpFocusRects(); + 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, const CachedNode** directHit, const CachedFrame** framePtr, int* x, int* y) 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.mMouseBounds = testData.mNodeBounds = test->getBounds(); + bool checkForHidden = true; + for (size_t part = 0; part < parts; part++) { + if (test->focusRings().at(part).intersects(rect)) { + if (checkForHidden && mRoot->maskIfHidden(&testData) == true) + break; + checkForHidden = false; + WebCore::IntRect testRect = test->focusRings().at(part); + testRect.intersect(testData.mMouseBounds); + 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->getBounds().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 focusring 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; + 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 (*best > distance) { + *best = distance; + 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, directHit, + framePtr, x, y); + 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, + int* best, const CachedFrame** framePtr, int* x, int* y) 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; + const WebCore::IntRect& testRect = test->hitBounds(); + if (testRect.intersects(rect) == false) + continue; + BestData testData; + testData.mNode = test; + testData.mMouseBounds = testData.mNodeBounds = testRect; + if (mRoot->maskIfHidden(&testData) == true) + continue; + const WebCore::IntRect& bounds = testData.mMouseBounds; + WebCore::IntPoint testCenter = WebCore::IntPoint(bounds.x() + + (bounds.width() >> 1), bounds.y() + (bounds.height() >> 1)); + int dx = testCenter.x() - center.x(); + int dy = testCenter.y() - center.y(); + int distance = dx * dx + dy * dy; + if (*best <= distance) + continue; + *best = distance; + result = test; + *framePtr = this; + const WebCore::IntRect& focusRect = test->focusRings().at(0); + *x = focusRect.x() + (focusRect.width() >> 1); + *y = focusRect.y() + (focusRect.height() >> 1); + } + for (const CachedFrame* frame = mCachedFrames.begin(); + frame != mCachedFrames.end(); frame++) { + const CachedNode* frameResult = frame->findBestHitAt(rect, best, + framePtr, x, y); + if (NULL != frameResult) + result = frameResult; + } + return result; +} + +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->isFocusable(*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->focusRings().at(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; + bestData->mMouseBounds = bestData->mNodeBounds = + test->focusRings().at(part); + 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); + } + } + } + } +} + +bool CachedFrame::finishInit() +{ + CachedNode* lastCached = lastNode(); + lastCached->setLast(); + CachedFrame* child = mCachedFrames.begin(); + while (child != mCachedFrames.end()) { + child->mParent = this; + if (child->finishInit()) + setFocusIndex(child->indexInParent()); + child++; + } + return focusIndex() > 0; +} + +const CachedNode* CachedFrame::frameDown(const CachedNode* test, const CachedNode* limit, BestData* bestData, + const CachedNode* focus) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameDown, test, bestData, focus)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData, focus) == REJECT_TEST) + continue; + if (checkVisited(test, DOWN) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.mNodeBounds = test->focusRings().at(part); + if (testData.setDownDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData, focus); + 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, focus); + if (checkVisited(innerData.mNode, DOWN)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, DOWN)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); + ASSERT(focus == NULL || bestData->mNode != focus); + // does the best contain something (or, is it contained by an area which is not the focus?) + // 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 CachedNode* focus) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameLeft, test, bestData, focus)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData, focus) == REJECT_TEST) + continue; + if (checkVisited(test, LEFT) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.mNodeBounds = test->focusRings().at(part); + if (testData.setLeftDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData, focus); + 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, focus); + 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(focus == NULL || bestData->mNode != focus); + return bestData->mNode; +} + +int CachedFrame::frameNodeCommon(BestData& testData, const CachedNode* test, BestData* bestData, BestData* originalData, + const CachedNode* focus) 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()) == false) { + testData.mNode->setCondition(CachedNode::FOCUSABLE); + return REJECT_TEST; + } +// if (isFocusable(test, &testData.mNodeBounds, walk) == false) { +// testData.mNode->setCondition(CachedNode::FOCUSABLE); +// return REJECT_TEST; +// } +// + if (test == focus) { + testData.mNode->setCondition(CachedNode::NOT_FOCUS_NODE); + return REJECT_TEST; + } +// if (test->bounds().contains(mRoot->focusBounds())) { +// testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); +// return REJECT_TEST; +// } + void* par = focus ? focus->parentGroup() : NULL; + testData.mFocusChild = test->parentGroup() == par; +#if 0 // not debugged + if (focus && focus->hasMouseOver() && test->hasMouseOver() == false && + focus->bounds().contains(test->bounds())) + return REJECT_TEST; +#endif + if (bestData->mNode == NULL) + return TEST_IS_BEST; +#if 0 // not debugged + if (focus && focus->hasMouseOver() && test->hasMouseOver() == false && + focus->bounds().contains(test->bounds())) + return REJECT_TEST; + if (test->hasMouseOver() != bestData->mNode->hasMouseOver()) { + if (test->hasMouseOver()) { + if (test->bounds().contains(bestData->mNode->bounds())) { + const_cast<CachedNode*>(bestData->mNode)->setDisabled(true); + bestData->mNode = NULL; // force part tests to be ignored, yet still set up remaining test data for later comparison + return TEST_IS_BEST; + } + } else { + if (bestData->mNode->bounds().contains(test->bounds())) { + test->setCondition(CachedNode::ANCHOR_IN_ANCHOR); + return REJECT_TEST; + } + } + } +#endif + if (focus && testData.mNode->parentIndex() != bestData->mNode->parentIndex()) { + int focusParentIndex = focus->parentIndex(); + if (focusParentIndex >= 0) { + if (bestData->mNode->parentIndex() == focusParentIndex) + return REJECT_TEST; + if (testData.mNode->parentIndex() == focusParentIndex) + 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 CachedNode* focus) const +{ + if (testData.mNodeBounds.contains(mRoot->focusBounds())) { + testData.mNode->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + return REJECT_TEST; + } + testData.setDistances(); + if (bestData->mNode != NULL) { + int compared = compare(testData, *bestData, focus); + 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 CachedNode* focus) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameRight, test, bestData, focus)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData, focus) == REJECT_TEST) + continue; + if (checkVisited(test, RIGHT) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.mNodeBounds = test->focusRings().at(part); + if (testData.setRightDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData, focus); + 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, focus); + if (checkVisited(innerData.mNode, RIGHT)) { + *bestData = innerData; + continue; + } + } + if (checkVisited(test, RIGHT)) + *bestData = testData; + } + } while ((test = test->traverseNextNode()) != limit); + ASSERT(focus == NULL || bestData->mNode != focus); + return bestData->mNode; +} + +const CachedNode* CachedFrame::frameUp(const CachedNode* test, const CachedNode* limit, BestData* bestData, + const CachedNode* focus) const +{ + BestData originalData = *bestData; + do { + if (moveInFrame(&CachedFrame::frameUp, test, bestData, focus)) + continue; + BestData testData; + if (frameNodeCommon(testData, test, bestData, &originalData, focus) == REJECT_TEST) + continue; + if (checkVisited(test, UP) == false) + continue; + size_t parts = test->navableRects(); + for (size_t part = 0; part < parts; part++) { + testData.mNodeBounds = test->focusRings().at(part); + if (testData.setUpDirection(history())) + continue; + int result = framePartCommon(testData, test, bestData, focus); + 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, focus); + 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(focus == NULL || bestData->mNode != focus); + return bestData->mNode; +} + +const CachedFrame* CachedFrame::hasFrame(const CachedNode* node) const +{ + return node->isFrame() ? &mCachedFrames[node->childFrameIndex()] : NULL; +} + +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; + mFocus = -1; + mFrame = frame; + mParent = NULL; // set up parents after stretchy arrays are set up + mIndex = childFrameIndex; +} + +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(); +} + +bool CachedFrame::moveInFrame(MoveInDirection moveInDirection, + const CachedNode* test, BestData* bestData, + const CachedNode* focus) 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, focus); + return true; +} + +const WebCore::IntRect& CachedFrame::_navBounds() const +{ + return history()->navBounds(); +} + +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(); + } +} + +bool CachedFrame::sameFrame(const CachedFrame* test) const +{ + ASSERT(test); + if (mIndex != test->mIndex) + return false; + if (mIndex == -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::setFocus(WebCore::Frame* frame, WebCore::Node* node, + int x, int y) +{ + if (NULL == node) { + const_cast<CachedRoot*>(mRoot)->setCachedFocus(NULL, NULL); + return true; + } + if (mFrame != frame) { + for (CachedFrame* testF = mCachedFrames.begin(); testF != mCachedFrames.end(); + testF++) { + if (testF->setFocus(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(); + WTF::Vector<WebCore::IntRect>& focusRings = test->focusRings(); + for (size_t part = 0; part < partMax; part++) { + const WebCore::IntRect& testBounds = focusRings.at(part); + if (testBounds.contains(x, y) == false) + continue; + if (test->isFocus()) { + DBG_NAV_LOGD("already set? test=%d frame=%p node=%p x=%d y=%d", + test->index(), frame, node, x, y); + return true; + } + const_cast<CachedRoot*>(mRoot)->setCachedFocus(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->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + 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->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + 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->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + 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->setCondition(CachedNode::NOT_ENCLOSING_FOCUS); + 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 focus + // prefer leftmost center + // if left and right > 0, test node subsumes focus + mNavDelta = left; + mNavDelta2 = right; +} + +void CachedFrame::BestData::setNavOverlap(int span, int left, int right) +{ + mNavOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; // if left or right < 0, test node is not in umbra of focus + 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) +{ + mWorkingOutside = left < MIN_OVERLAP || right < MIN_OVERLAP; // if left or right < 0, test node is not in umbra of focus + 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(); + DUMP_NAV_LOGD("// }; // end of nodes\n"); + 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 mIndex=%d;\n", b->mIndex); + DUMP_NAV_LOGD("// const CachedRoot* mRoot=%p;\n", b->mRoot); + DUMP_NAV_LOGD("// int mFocus=%d;\n", b->mFocus); +} + +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 + +} diff --git a/WebKit/android/nav/CachedFrame.h b/WebKit/android/nav/CachedFrame.h new file mode 100644 index 0000000..8e77470 --- /dev/null +++ b/WebKit/android/nav/CachedFrame.h @@ -0,0 +1,228 @@ +/* + * 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef CachedFrame_H +#define CachedFrame_H + +#include "CachedNode.h" +#include "IntRect.h" +#include "SkFixed.h" +#include "wtf/Vector.h" + +namespace WebCore { + class Frame; + class Node; +} + +namespace android { + +class CachedHistory; +class CachedRoot; + + // first node referenced by cache is always document +class CachedFrame { +public: + enum Direction { + UNINITIALIZED = -1, + LEFT, + RIGHT, + UP, + DOWN, + DIRECTION_COUNT, + DIRECTION_MASK = DIRECTION_COUNT - 1, + UP_DOWN = UP & DOWN, // mask and result + RIGHT_DOWN = RIGHT & DOWN, // mask and result + }; + enum Compare { + UNDECIDED = -1, + TEST_IS_BEST, + REJECT_TEST + }; + CachedFrame() {} + void add(CachedNode& node) { mCachedNodes.append(node); } + void addFrame(CachedFrame& child) { mCachedFrames.append(child); } + bool checkVisited(const CachedNode* , CachedFrame::Direction ) const; + size_t childCount() { return mCachedFrames.size(); } + void clearFocus(); + const CachedNode* currentFocus() const { return currentFocus(NULL); } + const CachedNode* currentFocus(const CachedFrame** ) const; + bool directionChange() const; + const CachedNode* document() const { return mCachedNodes.begin(); } + bool empty() const { return mCachedNodes.size() < 2; } // must have 1 past doc + const CachedNode* findBestAt(const WebCore::IntRect& , int* best, + const CachedNode** , const CachedFrame** , int* x, int* y) const; + const CachedFrame* findBestFrameAt(int x, int y) const; + const CachedNode* findBestHitAt(const WebCore::IntRect& , + int* best, const CachedFrame** , int* x, int* y) const; + bool finishInit(); + CachedFrame* firstChild() { return mCachedFrames.begin(); } + const CachedFrame* firstChild() const { return mCachedFrames.begin(); } + int focusIndex() const { return mFocus; } + void* framePointer() const { return mFrame; } + CachedNode* getIndex(int index) { return index >= 0 ? + &mCachedNodes[index] : NULL; } + const CachedFrame* hasFrame(const CachedNode* node) const; + int indexInParent() const { return mIndex; } + void init(const CachedRoot* root, int index, WebCore::Frame* frame); + const CachedFrame* lastChild() const { return &mCachedFrames.last(); } + CachedNode* lastNode() { return &mCachedNodes.last(); } + CachedFrame* lastChild() { return &mCachedFrames.last(); } + const CachedFrame* parent() const { return mParent; } + CachedFrame* parent() { return mParent; } + bool sameFrame(const CachedFrame* ) const; + void removeLast() { mCachedNodes.removeLast(); } + void resetClippedOut(); + void setContentsSize(int width, int height) { mContents.setWidth(width); + mContents.setHeight(height); } + void setData(); + bool setFocus(WebCore::Frame* , WebCore::Node* , int x, int y); + void setFocusIndex(int focusIndex) const { mFocus = focusIndex; } + void setLocalViewBounds(const WebCore::IntRect& bounds) { mLocalViewBounds = bounds; } + int size() { return mCachedNodes.size(); } + const CachedNode* validDocument() const; +protected: + struct BestData { + WebCore::IntRect mNodeBounds; + WebCore::IntRect mMouseBounds; + int mDistance; + int mSideDistance; + int mMajorDelta; // difference of center of object + // used only when leading and trailing edges contain another set of edges + int mMajorDelta2; // difference of leading edge (only used when center is same) + int mMajorButt; // checks for next cell butting up against or close to previous one + int mWorkingDelta; + int mWorkingDelta2; + int mNavDelta; + int mNavDelta2; + const CachedFrame* mFrame; + const CachedNode* mNode; + SkFixed mWorkingOverlap; // this and below are fuzzy answers instead of bools + SkFixed mNavOverlap; + SkFixed mPreferred; + bool mFocusChild; + bool mInNav; + bool mNavOutside; + bool mWorkingOutside; + int bottom() const { return bounds().bottom(); } + const WebCore::IntRect& bounds() const { return mNodeBounds; } + bool canBeReachedByAnotherDirection(); + int height() const { return bounds().height(); } + bool inOrSubsumesNav() const { return (mNavDelta ^ mNavDelta2) >= 0; } + bool inOrSubsumesWorking() const { return (mWorkingDelta ^ mWorkingDelta2) >= 0; } + int isContainer(BestData* ); + static SkFixed Overlap(int span, int left, int right); + void reset() { mNode = NULL; } + int right() const { return bounds().right(); } + void setDistances(); + bool setDownDirection(const CachedHistory* ); + bool setLeftDirection(const CachedHistory* ); + bool setRightDirection(const CachedHistory* ); + bool setUpDirection(const CachedHistory* ); + void setNavInclusion(int left, int right); + void setNavOverlap(int span, int left, int right); + void setWorkingInclusion(int left, int right); + void setWorkingOverlap(int span, int left, int right); + int width() const { return bounds().width(); } + int x() const { return bounds().x(); } + int y() const { return bounds().y(); } + }; + typedef const CachedNode* (CachedFrame::*MoveInDirection)( + const CachedNode* test, const CachedNode* limit, BestData* bestData, + const CachedNode* focus) const; + void adjustToTextColumn(int* delta) const; + static bool CheckBetween(Direction , const WebCore::IntRect& bestRect, + const WebCore::IntRect& prior, WebCore::IntRect* result); + bool checkBetween(BestData* , Direction ); + int compare(BestData& testData, const BestData& bestData, const + CachedNode* focus) const; + void findClosest(BestData* , Direction original, Direction test, + WebCore::IntRect* clip) const; + int frameNodeCommon(BestData& testData, const CachedNode* test, + BestData* bestData, BestData* originalData, + const CachedNode* focus) const; + int framePartCommon(BestData& testData, const CachedNode* test, + BestData* bestData, const CachedNode* focus) const; + const CachedNode* frameDown(const CachedNode* test, const CachedNode* limit, + BestData* , const CachedNode* focus) const; + const CachedNode* frameLeft(const CachedNode* test, const CachedNode* limit, + BestData* , const CachedNode* focus) const; + const CachedNode* frameRight(const CachedNode* test, const CachedNode* limit, + BestData* , const CachedNode* focus) const; + const CachedNode* frameUp(const CachedNode* test, const CachedNode* limit, + BestData* , const CachedNode* focus) const; + int minWorkingHorizontal() const; + int minWorkingVertical() const; + int maxWorkingHorizontal() const; + int maxWorkingVertical() const; + bool moveInFrame(MoveInDirection , const CachedNode* test, BestData* best, + const CachedNode* focus) const; + const WebCore::IntRect& _navBounds() const; + WebCore::IntRect mContents; + WebCore::IntRect mLocalViewBounds; + WebCore::IntRect mViewBounds; + WTF::Vector<CachedNode> mCachedNodes; + WTF::Vector<CachedFrame> mCachedFrames; + void* mFrame; // WebCore::Frame*, used only to compare pointers + CachedFrame* mParent; + int mIndex; // index within parent's array of children, or -1 if root + const CachedRoot* mRoot; + mutable int mFocus; +private: + CachedHistory* history() const; +#ifdef BROWSER_DEBUG +public: + CachedNode* find(WebCore::Node* ); // !!! probably debugging only + int mDebugIndex; + int mDebugLoopbackOffset; +#endif +#if !defined NDEBUG || DUMP_NAV_CACHE +public: + class Debug { +public: + Debug() { +#if DUMP_NAV_CACHE + mFrameName[0] = '\0'; +#endif +#if !defined NDEBUG + mInUse = true; +#endif + } +#if !defined NDEBUG + ~Debug() { mInUse = false; } + bool mInUse; +#endif +#if DUMP_NAV_CACHE + CachedFrame* base() const; + void print() const; + bool validate(const CachedNode* ) const; + char mFrameName[256]; +#endif + } mDebug; +#endif +}; + +} + +#endif diff --git a/WebKit/android/nav/CachedHistory.cpp b/WebKit/android/nav/CachedHistory.cpp new file mode 100644 index 0000000..f75f237 --- /dev/null +++ b/WebKit/android/nav/CachedHistory.cpp @@ -0,0 +1,203 @@ +/* + * 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 APPLE COMPUTER, INC. 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 "CachedFrame.h" +#include "CachedNode.h" +#if DUMP_NAV_CACHE +#include "CachedRoot.h" +#endif + +#include "CachedHistory.h" + +namespace android { + +CachedHistory::CachedHistory() { + memset(this, 0, sizeof(CachedHistory)); // this assume the class has no virtuals + mLastMove = CachedFrame::UNINITIALIZED; + mPriorMove = CachedFrame::UNINITIALIZED; +} + + +void CachedHistory::addToVisited(const CachedNode* node, CachedFrame::Direction direction) +{ + memmove(&mVisited[1], &mVisited[0], sizeof(mVisited) - sizeof(mVisited[0])); + mVisited[0].mNode = node; + mVisited[0].mDirection = direction; +} + +bool CachedHistory::checkVisited(const CachedNode* node, CachedFrame::Direction direction) const +{ + // if the direction is unchanged and we've already visited this node, don't visit it again + int index = 0; + while (index < NAVIGATION_VISIT_DEPTH - 1) { + if (direction != mVisited[index].mDirection) + break; + index++; // compare with last direction, previous to last node (where the arrow took us from) + if (node == mVisited[index].mNode) + return false; + } + return true; +} + +void CachedHistory::pinMaxMin(const WebCore::IntRect& viewBounds) +{ + if (mMinWorkingHorizontal < viewBounds.y() || + mMinWorkingHorizontal >= viewBounds.bottom()) + mMinWorkingHorizontal = viewBounds.y(); + if (mMaxWorkingHorizontal > viewBounds.bottom() || + mMaxWorkingHorizontal <= viewBounds.y()) + mMaxWorkingHorizontal = viewBounds.bottom(); + if (mMinWorkingVertical < viewBounds.x() || + mMinWorkingVertical >= viewBounds.right()) + mMinWorkingVertical = viewBounds.x(); + if (mMaxWorkingVertical > viewBounds.right() || + mMaxWorkingVertical <= viewBounds.x()) + mMaxWorkingVertical = viewBounds.right(); +} + +void CachedHistory::reset() +{ + memset(mVisited, 0, sizeof(mVisited)); +// mLastScroll = 0; + mPriorBounds = WebCore::IntRect(0, 0, 0, 0); + mDirectionChange = false; + mFocusIsInput = false; + mPriorIsInput = false; + mDidFirstLayout = false; + mPriorMove = mLastMove = CachedFrame::UNINITIALIZED; + mMinWorkingHorizontal = mMinWorkingVertical = INT_MIN; + mMaxWorkingHorizontal = mMaxWorkingVertical = INT_MAX; +} + +void CachedHistory::setWorking(CachedFrame::Direction newMove, + const CachedNode* focus, const WebCore::IntRect& viewBounds) +{ + CachedFrame::Direction lastAxis = (CachedFrame::Direction) (mLastMove & ~CachedFrame::RIGHT_DOWN); // up, left or uninitialized + CachedFrame::Direction newAxis = (CachedFrame::Direction) (newMove & ~CachedFrame::RIGHT_DOWN); + bool change = newAxis != lastAxis; + mDirectionChange = change && mLastMove != CachedFrame::UNINITIALIZED; + if (focus != NULL || mLastMove != CachedFrame::UNINITIALIZED) { + mPriorMove = mLastMove; + mLastMove = newMove; + } + const WebCore::IntRect* navBounds = &mNavBounds; + if (focus != NULL) { + WebCore::IntRect focusBounds; + focus->getBounds(&focusBounds); + if (focusBounds.isEmpty() == false) + mNavBounds = focusBounds; + mPriorIsInput = mFocusIsInput; + mFocusIsInput = focus->isInput(); // focus->localName() == "input"; + } + if (change) { // uninitialized or change in direction + if (lastAxis != CachedFrame::LEFT && navBounds->height() > 0) { + mMinWorkingHorizontal = navBounds->y(); + mMaxWorkingHorizontal = navBounds->bottom(); + } + if (lastAxis != CachedFrame::UP && navBounds->width() > 0) { + mMinWorkingVertical = navBounds->x(); + mMaxWorkingVertical = navBounds->right(); + } + } else if (mPriorIsInput) { + if (newAxis == CachedFrame::UP_DOWN) { + if (mPriorBounds.x() == mMinWorkingVertical && mPriorBounds.right() == mMaxWorkingVertical) { + mMinWorkingVertical = navBounds->x(); + mMaxWorkingVertical = navBounds->right(); + } + } else { + if (mPriorBounds.y() == mMinWorkingHorizontal && mPriorBounds.bottom() == mMaxWorkingHorizontal) { + mMinWorkingHorizontal = navBounds->y(); + mMaxWorkingHorizontal = navBounds->bottom(); + } + } + } + pinMaxMin(viewBounds); +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +#define DEBUG_PRINT_RECT(field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ + r.x(), r.y(), r.width(), r.height()); } + +CachedHistory* CachedHistory::Debug::base() const { + CachedHistory* nav = (CachedHistory*) ((char*) this - OFFSETOF(CachedHistory, mDebug)); + return nav; +} + +const char* CachedHistory::Debug::direction(CachedFrame::Direction d) const +{ + switch (d) { + case CachedFrame::LEFT: return "LEFT"; break; + case CachedFrame::RIGHT: return "RIGHT"; break; + case CachedFrame::UP: return "UP"; break; + case CachedFrame::DOWN: return "DOWN"; break; + default: return "UNINITIALIZED"; + } +} + +void CachedHistory::Debug::print(CachedRoot* root) const +{ + CachedHistory* b = base(); + DUMP_NAV_LOGD("// Visited mVisited[]={\n"); + for (size_t i = 0; i < NAVIGATION_VISIT_DEPTH; i++) { + const Visited& visit = b->mVisited[i]; + const CachedNode* node = visit.mNode; + int index = root != NULL && root->CachedFrame::mDebug.validate(node) ? + node->index() : -1; + DUMP_NAV_LOGD(" // { 0x%p (%d), %s },\n", node, index, direction(visit.mDirection)); + } + DUMP_NAV_LOGD("// };\n"); +// DUMP_NAV_LOGD("// int mLastScroll=%d;\n", b->mLastScroll); + DEBUG_PRINT_RECT(mNavBounds); + DEBUG_PRINT_RECT(mPriorBounds); + DEBUG_PRINT_BOOL(mDirectionChange); + DEBUG_PRINT_BOOL(mFocusIsInput); + DEBUG_PRINT_BOOL(mPriorIsInput); + DUMP_NAV_LOGD("// CachedFrame::Direction mLastMove=%s, mPriorMove=%s;\n", + direction(b->mLastMove), direction(b->mPriorMove)); + int max = b->mMaxWorkingHorizontal; + DUMP_NAV_LOGD("static int TEST_MAX_H = %d;\n", max); + int min = b->mMinWorkingHorizontal; + if (min == INT_MIN) + min++; + DUMP_NAV_LOGD("static int TEST_MIN_H = %d;\n", min); + max = b->mMaxWorkingVertical; + DUMP_NAV_LOGD("static int TEST_MAX_V = %d;\n", max); + min = b->mMinWorkingVertical; + if (min == INT_MIN) + min++; + DUMP_NAV_LOGD("static int TEST_MIN_V = %d;\n", min); + DUMP_NAV_LOGD("\n"); +} + +#endif + +} diff --git a/WebKit/android/nav/CachedHistory.h b/WebKit/android/nav/CachedHistory.h new file mode 100644 index 0000000..e48d44b --- /dev/null +++ b/WebKit/android/nav/CachedHistory.h @@ -0,0 +1,90 @@ +/* + * 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef CachedHistory_H +#define CachedHistory_H + +#include "CachedFrame.h" + +#define NAVIGATION_VISIT_DEPTH 8 // the number of nodes last visited -- used to detect ping-ponging (number should be tuned) + +namespace android { + +class CachedRoot; + +// CachedHistory is maintained even if DOM is rebuilt by running script. +// It uses blind pointers for comparison in the previously visited nodes. +class CachedHistory { +public: + CachedHistory(); + void addToVisited(const CachedNode* , CachedFrame::Direction ); + bool checkVisited(const CachedNode* , CachedFrame::Direction ) const; + bool didFirstLayout() const { return mDidFirstLayout; } + bool directionChange() const { return mDirectionChange; } + int minWorkingHorizontal() const { return mMinWorkingHorizontal; } + int minWorkingVertical() const { return mMinWorkingVertical; } + int maxWorkingHorizontal() const { return mMaxWorkingHorizontal; } + int maxWorkingVertical() const { return mMaxWorkingVertical; } + const WebCore::IntRect& navBounds() const { return mNavBounds; } + const WebCore::IntRect& priorBounds() const { return mPriorBounds; } + void setDidFirstLayout(bool did) { mDidFirstLayout = did; } + void setNavBounds(const WebCore::IntRect& loc) { mNavBounds = loc; } + void setWorking(CachedFrame::Direction , const CachedNode* focus, + const WebCore::IntRect& viewBounds); + void reset(); +private: + void pinMaxMin(const WebCore::IntRect& viewBounds); + struct Visited { + const CachedNode* mNode; + CachedFrame::Direction mDirection; + } mVisited[NAVIGATION_VISIT_DEPTH]; + WebCore::IntRect mMouseBounds; // constricted bounds, if focus ring is partially visible + WebCore::IntRect mNavBounds; // focus ring bounds plus optional keystroke movement + WebCore::IntRect mPriorBounds; // prior chosen focus ring (for reversing narrowing) + bool mDirectionChange; + bool mFocusIsInput; // defer max/min to non-focus node if focus is too broad + bool mPriorIsInput; // defer max/min to non-focus node if focus is too broad + bool mDidFirstLayout; // set true when page is newly laid out + CachedFrame::Direction mLastMove; + CachedFrame::Direction mPriorMove; + int mMinWorkingHorizontal; + int mMaxWorkingHorizontal; + int mMinWorkingVertical; + int mMaxWorkingVertical; + friend class CachedRoot; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedHistory* base() const; + const char* direction(CachedFrame::Direction d) const; + void print(CachedRoot* ) const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/WebKit/android/nav/CachedNode.cpp b/WebKit/android/nav/CachedNode.cpp new file mode 100644 index 0000000..b786677 --- /dev/null +++ b/WebKit/android/nav/CachedNode.cpp @@ -0,0 +1,346 @@ +/* + * 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 APPLE COMPUTER, INC. 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 "CachedFrame.h" +#include "CachedHistory.h" +#include "Node.h" +#include "PlatformString.h" + +#include "android_graphics.h" +#include "CachedNode.h" + +namespace android { + +void CachedNode::clearFocus(CachedFrame* parent) +{ + if (isFrame()) { + CachedFrame* child = const_cast<CachedFrame*>(parent->hasFrame(this)); + child->clearFocus(); + } + mIsFocus = false; +} + +bool CachedNode::Clip(const WebCore::IntRect& outer, WebCore::IntRect* inner, + WTF::Vector<WebCore::IntRect>* rings) +{ + if (outer.contains(*inner)) + return true; +// DBG_NAV_LOGD("outer:{%d,%d,%d,%d} does not contain inner:{%d,%d,%d,%d}", +// outer.x(), outer.y(), outer.width(), outer.height(), +// inner->x(), inner->y(), inner->width(), inner->height()); + bool intersects = outer.intersects(*inner); + size_t size = intersects ? rings->size() : 0; + *inner = WebCore::IntRect(0, 0, 0, 0); + if (intersects) { + WebCore::IntRect * const start = rings->begin(); + WebCore::IntRect* ring = start + size - 1; + do { + ring->intersect(outer); + if (ring->isEmpty()) { + if ((size_t) (ring - start) != --size) + *ring = start[size]; + } else + inner->unite(*ring); + } while (ring-- != start); + } + rings->shrink(size); +// DBG_NAV_LOGD("size:%d", size); + return size != 0; +} + +bool CachedNode::clip(const WebCore::IntRect& bounds) +{ + return Clip(bounds, &mBounds, &mFocusRing); +} + +#define OVERLAP 3 + +void CachedNode::fixUpFocusRects() +{ + if (mFixedUpFocusRects) + return; + mFixedUpFocusRects = true; + if (mNavableRects <= 1) + return; +#if DEBUG_NAV_UI + { + WebCore::IntRect* boundsPtr = mFocusRing.begin() - 1; + const WebCore::IntRect* const boundsEnd = mFocusRing.begin() + mFocusRing.size(); + while (++boundsPtr < boundsEnd) + LOGD("%s %d:(%d, %d, %d, %d)\n", __FUNCTION__, boundsPtr - mFocusRing.begin(), + boundsPtr->x(), boundsPtr->y(), boundsPtr->width(), boundsPtr->height()); + } +#endif + // q: need to know when rects are for drawing and hit-testing, but not mouse down calcs? + bool again; + do { + again = false; + size_t size = mFocusRing.size(); + WebCore::IntRect* unitBoundsPtr = mFocusRing.begin() - 1; + const WebCore::IntRect* const unitBoundsEnd = mFocusRing.begin() + size; + while (++unitBoundsPtr < unitBoundsEnd) { + // any other unitBounds to the left or right of this one? + int unitTop = unitBoundsPtr->y(); + int unitBottom = unitBoundsPtr->bottom(); + int unitLeft = unitBoundsPtr->x(); + int unitRight = unitBoundsPtr->right(); + WebCore::IntRect* testBoundsPtr = mFocusRing.begin() - 1; + while (++testBoundsPtr < unitBoundsEnd) { + if (unitBoundsPtr == testBoundsPtr) + continue; + int testTop = testBoundsPtr->y(); + int testBottom = testBoundsPtr->bottom(); + int testLeft = testBoundsPtr->x(); + int testRight = testBoundsPtr->right(); + int candidateTop = unitTop > testTop ? unitTop : testTop; + int candidateBottom = unitBottom < testBottom ? unitBottom : testBottom; + int candidateLeft = unitRight < testLeft ? unitRight : testRight; + int candidateRight = unitRight > testLeft ? unitLeft : testLeft; + bool leftRight = true; + if (candidateTop + OVERLAP >= candidateBottom || + candidateLeft + OVERLAP >= candidateRight) { + candidateTop = unitBottom < testTop ? unitBottom : testBottom; + candidateBottom = unitBottom > testTop ? unitTop : testTop; + candidateLeft = unitLeft > testLeft ? unitLeft : testLeft; + candidateRight = unitRight < testRight ? unitRight : testRight; + if (candidateTop + OVERLAP >= candidateBottom || + candidateLeft + OVERLAP >= candidateRight) + continue; + leftRight = false; + } + // construct candidate to add + WebCore::IntRect candidate = WebCore::IntRect(candidateLeft, candidateTop, + candidateRight - candidateLeft, candidateBottom - candidateTop); + // does a different unit bounds intersect the candidate? if so, don't add + WebCore::IntRect* checkBoundsPtr = mFocusRing.begin() - 1; + while (++checkBoundsPtr < unitBoundsEnd) { + if (checkBoundsPtr->intersects(candidate) == false) + continue; + if (leftRight) { + if (candidateTop >= checkBoundsPtr->y() && + candidateBottom > checkBoundsPtr->bottom()) + candidateTop = checkBoundsPtr->bottom(); + else if (candidateTop < checkBoundsPtr->y() && + candidateBottom <= checkBoundsPtr->bottom()) + candidateBottom = checkBoundsPtr->y(); + else + goto nextCheck; + } else { + if (candidateLeft >= checkBoundsPtr->x() && + candidateRight > checkBoundsPtr->right()) + candidateLeft = checkBoundsPtr->right(); + else if (candidateLeft < checkBoundsPtr->x() && + candidateRight <= checkBoundsPtr->right()) + candidateRight = checkBoundsPtr->x(); + else + goto nextCheck; + } + } + candidate = WebCore::IntRect(candidateLeft, candidateTop, + candidateRight - candidateLeft, candidateBottom - candidateTop); + ASSERT(candidate.isEmpty() == false); +#if DEBUG_NAV_UI + LOGD("%s %d:(%d, %d, %d, %d)\n", __FUNCTION__, mFocusRing.size(), + candidate.x(), candidate.y(), candidate.width(), candidate.height()); +#endif + mFocusRing.append(candidate); + again = true; + goto tryAgain; + nextCheck: + continue; + } + } +tryAgain: + ; + } while (again); +} + + +void CachedNode::focusRingBounds(WebCore::IntRect* bounds) const +{ + int partMax = mNavableRects; + ASSERT(partMax > 0); + *bounds = mFocusRing[0]; + for (int partIndex = 1; partIndex < partMax; partIndex++) + bounds->unite(mFocusRing[partIndex]); + bounds->inflate(FOCUS_RING_HIT_TEST_RADIUS); +} + +void CachedNode::init(WebCore::Node* node) +{ + bzero(this, sizeof(CachedNode)); + mExport = WebCore::String(); + mName = WebCore::String(); + mNode = node; + mParentIndex = mChildFrameIndex = -1; + mType = android::NORMAL_CACHEDNODETYPE; +} + +void CachedNode::move(int x, int y) +{ + mBounds.move(x, y); + // mHitTestBounds will be moved by caller + WebCore::IntRect* first = mFocusRing.begin(); + WebCore::IntRect* last = first + mFocusRing.size(); + --first; + while (++first != last) + first->move(x, y); +} + +bool CachedNode::partRectsContains(const CachedNode* other) const +{ + int outerIndex = 0; + int outerMax = mNavableRects; + int innerMax = other->mNavableRects; + do { + const WebCore::IntRect& outerBounds = mFocusRing[outerIndex]; + int innerIndex = 0; + do { + const WebCore::IntRect& innerBounds = other->mFocusRing[innerIndex]; + if (innerBounds.contains(outerBounds)) + return true; + } while (++innerIndex < innerMax); + } while (++outerIndex < outerMax); + return false; +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +#define DEBUG_PRINT_RECT(field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ + r.x(), r.y(), r.width(), r.height()); } + +CachedNode* CachedNode::Debug::base() const { + CachedNode* nav = (CachedNode*) ((char*) this - OFFSETOF(CachedNode, mDebug)); + return nav; +} + +const char* CachedNode::Debug::condition(Condition t) const +{ + switch (t) { + case NOT_REJECTED: return "NOT_REJECTED"; break; + case BUTTED_UP: return "BUTTED_UP"; break; + case CENTER_FURTHER: return "CENTER_FURTHER"; break; + case CLOSER: return "CLOSER"; break; + case CLOSER_IN_FOCUS: return "CLOSER_IN_FOCUS"; break; + case CLOSER_OVERLAP: return "CLOSER_OVERLAP"; break; + case CLOSER_TOP: return "CLOSER_TOP"; break; + case FOCUSABLE: return "FOCUSABLE"; break; + case FURTHER: return "FURTHER"; break; + case IN_UMBRA: return "IN_UMBRA"; break; + case IN_WORKING: return "IN_WORKING"; break; + case LEFTMOST: return "LEFTMOST"; break; + case OVERLAP_OR_EDGE_FURTHER: return "OVERLAP_OR_EDGE_FURTHER"; break; + case PREFERRED: return "PREFERRED"; break; + case ANCHOR_IN_ANCHOR: return "ANCHOR_IN_ANCHOR"; break; + case BEST_DIRECTION: return "BEST_DIRECTION"; break; + case CHILD: return "CHILD"; break; + case DISABLED: return "DISABLED"; break; + case HIGHER_TAB_INDEX: return "HIGHER_TAB_INDEX"; break; + case IN_FOCUS: return "IN_FOCUS"; break; + case IN_FOCUS_CHILDREN: return "IN_FOCUS_CHILDREN"; break; + case NOT_ENCLOSING_FOCUS: return "NOT_ENCLOSING_FOCUS"; break; + // case NOT_FOCUS_CHILD: return "NOT_FOCUS_CHILD"; break; + case NOT_FOCUS_NODE: return "NOT_FOCUS_NODE"; break; + case OUTSIDE_OF_BEST: return "OUTSIDE_OF_BEST"; break; + case OUTSIDE_OF_ORIGINAL: return "OUTSIDE_OF_ORIGINAL"; break; + default: return "???"; + } +} + +const char* CachedNode::Debug::type(android::CachedNodeType t) const +{ + switch (t) { + case NORMAL_CACHEDNODETYPE: return "NORMAL"; break; + case ADDRESS_CACHEDNODETYPE: return "ADDRESS"; break; + case EMAIL_CACHEDNODETYPE: return "EMAIL"; break; + case PHONE_CACHEDNODETYPE: return "PHONE"; break; + default: return "???"; + } +} + +void CachedNode::Debug::print() const +{ + CachedNode* b = base(); + char scratch[256]; + size_t index = snprintf(scratch, sizeof(scratch), "// char* mExport=\""); + const UChar* ch = b->mExport.characters(); + while (ch && *ch && index < sizeof(scratch)) + scratch[index++] = *ch++; + DUMP_NAV_LOGD("%.*s\"\n", index, scratch); + index = snprintf(scratch, sizeof(scratch), "// char* mName=\""); + ch = b->mName.characters(); + while (ch && *ch && index < sizeof(scratch)) + scratch[index++] = *ch++; + DUMP_NAV_LOGD("%.*s\"\n", index, scratch); + DEBUG_PRINT_RECT(mBounds); + DEBUG_PRINT_RECT(mHitBounds); + const WTF::Vector<WebCore::IntRect>& rects = b->focusRings(); + size_t size = rects.size(); + DUMP_NAV_LOGD("// IntRect focusRings={ // size=%d\n", size); + for (size_t i = 0; i < size; i++) + DUMP_NAV_LOGD(" // {%d, %d, %d, %d}, // %d\n", rects[i].x(), rects[i].y(), + rects[i].width(), rects[i].height(), i); + DUMP_NAV_LOGD("// };\n"); + DUMP_NAV_LOGD("// void* mNode=%p; // (%d) \n", b->mNode, mNodeIndex); + DUMP_NAV_LOGD("// void* mParentGroup=%p; // (%d) \n", b->mParentGroup, mParentGroupIndex); + DUMP_NAV_LOGD("// int mChildFrameIndex=%d;\n", b->mChildFrameIndex); + DUMP_NAV_LOGD("// int mIndex=%d;\n", b->mIndex); + DUMP_NAV_LOGD("// int mMaxLength=%d;\n", b->mMaxLength); + DUMP_NAV_LOGD("// int mNavableRects=%d;\n", b->mNavableRects); + DUMP_NAV_LOGD("// int mParentIndex=%d;\n", b->mParentIndex); + DUMP_NAV_LOGD("// int mTextSize=%d;\n", b->mTextSize); + DUMP_NAV_LOGD("// int mTabIndex=%d;\n", b->mTabIndex); + DUMP_NAV_LOGD("// Condition mCondition=%s;\n", condition(b->mCondition)); + DUMP_NAV_LOGD("// Type mType=%s;\n", type(b->mType)); + DEBUG_PRINT_BOOL(mClippedOut); + DEBUG_PRINT_BOOL(mDisabled); + DEBUG_PRINT_BOOL(mFixedUpFocusRects); + DEBUG_PRINT_BOOL(mHasFocusRing); + DEBUG_PRINT_BOOL(mHasMouseOver); + DEBUG_PRINT_BOOL(mIsAnchor); + DEBUG_PRINT_BOOL(mIsArea); + DEBUG_PRINT_BOOL(mIsFocus); + DEBUG_PRINT_BOOL(mIsInput); + DEBUG_PRINT_BOOL(mIsParentAnchor); + DEBUG_PRINT_BOOL(mIsPassword); + DEBUG_PRINT_BOOL(mIsRtlText); + DEBUG_PRINT_BOOL(mIsTextArea); + DEBUG_PRINT_BOOL(mIsTextField); + DEBUG_PRINT_BOOL(mIsTransparent); + DEBUG_PRINT_BOOL(mIsUnclipped); + DEBUG_PRINT_BOOL(mLast); + DEBUG_PRINT_BOOL(mWantsKeyEvents); + DUMP_NAV_LOGD("\n"); +} + +#endif + +} diff --git a/WebKit/android/nav/CachedNode.h b/WebKit/android/nav/CachedNode.h new file mode 100644 index 0000000..aa64982 --- /dev/null +++ b/WebKit/android/nav/CachedNode.h @@ -0,0 +1,235 @@ +/* + * 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef CachedNode_H +#define CachedNode_H + +#include "AtomicString.h" +#include "CachedDebug.h" +#include "CachedNodeType.h" +#include "IntRect.h" +#include "PlatformString.h" +#include "wtf/Vector.h" + +namespace WebCore { + class Node; +} + +namespace android { + +class CachedFrame; + +class CachedNode { +public: +// Nodes are rejected because either they are spacially not the best (first set) +// or because they have the wrong DOM attribute (in focus, a focused child, etc) +// findClosest() gives only spacially rejected nodes a second chance + enum Condition { // if bigger than 32, increase bitfield size below + // rejections that get a second chance + NOT_REJECTED = 0, + SECOND_CHANCE_START = NOT_REJECTED, // must be first in list + BUTTED_UP, + CENTER_FURTHER, + CLOSER, + CLOSER_IN_FOCUS, + CLOSER_OVERLAP, + CLOSER_TOP, + FOCUSABLE, + FURTHER, + IN_UMBRA, + IN_WORKING, + LEFTMOST, + OVERLAP_OR_EDGE_FURTHER, + PREFERRED, // better overlap measure + SECOND_CHANCE_END = PREFERRED, // must be last in list + // rejections that don't get a second chance + ANCHOR_IN_ANCHOR, + BEST_DIRECTION, // can be reached by another direction + CHILD, + DISABLED, + HIGHER_TAB_INDEX, + IN_FOCUS, + IN_FOCUS_CHILDREN, + NOT_ENCLOSING_FOCUS, + // NOT_FOCUS_CHILD, + NOT_FOCUS_NODE, + OUTSIDE_OF_BEST, // containership + OUTSIDE_OF_ORIGINAL, // containership + CONDITION_SIZE // FIXME: test that CONDITION_SIZE fits in mCondition + }; + CachedNode() { + // The node is initiaized to 0 in its array, so nothing to do in the + // constructor + } + + const WebCore::IntRect& bounds() const { return mBounds; } + WebCore::IntRect* boundsPtr() { return &mBounds; } + int childFrameIndex() const { return mChildFrameIndex; } + void clearCondition() const { mCondition = NOT_REJECTED; } + void clearFocus(CachedFrame* ); + static bool Clip(const WebCore::IntRect& outer, WebCore::IntRect* inner, + WTF::Vector<WebCore::IntRect>* rings); + bool clip(const WebCore::IntRect& ); + bool clippedOut() { return mClippedOut; } + bool disabled() const { return mDisabled; } + const CachedNode* document() const { return &this[-mIndex]; } + void fixUpFocusRects(); + void focusRingBounds(WebCore::IntRect* ) const; + WTF::Vector<WebCore::IntRect>& focusRings() { return mFocusRing; } + const WTF::Vector<WebCore::IntRect>& focusRings() const { return mFocusRing; } + const WebCore::IntRect& getBounds() const { return mBounds; } + void getBounds(WebCore::IntRect* bounds) const { *bounds = mBounds; } + const WebCore::String& getExport() const { return mExport; } + bool hasFocusRing() const { return mHasFocusRing; } + bool hasMouseOver() const { return mHasMouseOver; } + const WebCore::IntRect& hitBounds() const { return mHitBounds; } + int index() const { return mIndex; } + void init(WebCore::Node* node); + bool isAnchor() const { return mIsAnchor; } + bool isArea() const { return mIsArea; } + bool isFocus() const { return mIsFocus; } + bool isFocusable(const WebCore::IntRect& clip) const { + return clip.intersects(mBounds); + } + bool isFrame() const { return mChildFrameIndex >= 0 ; } + bool isInput() const { return mIsInput; } + bool isPassword() const { return mIsPassword; } + bool isRtlText() const { return mIsRtlText; } + bool isTextArea() const { return mIsTextArea; } + bool isTextField() const { return mIsTextField; } + bool isTransparent() const { return mIsTransparent; } + bool isUnclipped() const { return mIsUnclipped; } + bool isWantsKeyEvents() const { return mWantsKeyEvents; } + + int maxLength() const { return mMaxLength; }; + void move(int x, int y); + const WebCore::String& name() const { return mName; } + int navableRects() const { return mNavableRects; } + void* nodePointer() const { return mNode; } + bool noSecondChance() const { return mCondition > SECOND_CHANCE_END; } + const CachedNode* parent() const { return document() + mParentIndex; } + void* parentGroup() const { return mParentGroup; } + int parentIndex() const { return mParentIndex; } + bool partRectsContains(const CachedNode* other) const; + void reset(); + void setBounds(const WebCore::IntRect& bounds) { mBounds = bounds; } + void setChildFrameIndex(int index) { mChildFrameIndex = index; } + void setClippedOut(bool clipped) { mClippedOut = clipped; } + void setCondition(Condition condition) const { mCondition = condition; } + void setDisabled(bool disabled) { mDisabled = disabled; } + void setExport(const WebCore::String& exported) { mExport = exported; } + void setHasFocusRing(bool hasFocusRing) { mHasFocusRing = hasFocusRing; } + void setHasMouseOver(bool hasMouseOver) { mHasMouseOver = hasMouseOver; } + void setHitBounds(const WebCore::IntRect& bounds) { mHitBounds = bounds; } + void setIndex(int index) { mIndex = index; } + void setIsAnchor(bool isAnchor) { mIsAnchor = isAnchor; } + void setIsArea(bool isArea) { mIsArea = isArea; } + void setIsFocus(bool isFocus) { mIsFocus = isFocus; } + void setIsInput(bool isInput) { mIsInput = isInput; } + void setIsParentAnchor(bool isAnchor) { mIsParentAnchor = isAnchor; } + void setIsPassword(bool isPassword) { mIsPassword = isPassword; } + void setIsRtlText(bool isRtlText) { mIsRtlText = isRtlText; } + void setIsTextArea(bool isTextArea) { mIsTextArea = isTextArea; } + void setIsTextField(bool isTextField) { mIsTextField = isTextField; } + void setIsTransparent(bool isTransparent) { mIsTransparent = isTransparent; } + void setIsUnclipped(bool unclipped) { mIsUnclipped = unclipped; } + void setLast() { mLast = true; } + void setMaxLength(int maxLength) { mMaxLength = maxLength; } + void setName(const WebCore::String& name) { mName = name; } + void setNavableRects() { mNavableRects = mFocusRing.size(); } + void setParentGroup(void* group) { mParentGroup = group; } + void setParentIndex(int parent) { mParentIndex = parent; } + void setTabIndex(int index) { mTabIndex = index; } + void setTextSize(int textSize) { mTextSize = textSize; } + void setType(CachedNodeType type) { mType = type; } + void setWantsKeyEvents(bool wantsKeys) { mWantsKeyEvents = wantsKeys; } + int tabIndex() const { return mTabIndex; } + const CachedNode* traverseNextNode() const { return mLast ? NULL : &this[1]; } + int textSize() const { return mTextSize; } + CachedNodeType type() const { return mType; } +private: + WebCore::String mExport; + WebCore::String mName; + WebCore::IntRect mBounds; + WebCore::IntRect mHitBounds; + WTF::Vector<WebCore::IntRect> mFocusRing; + void* mNode; // WebCore::Node*, only used to match pointers + void* mParentGroup; // WebCore::Node*, only used to match pointers + int mChildFrameIndex; // set to -1 if node is not a frame + int mIndex; // index of itself, to find first in array (document) + int mMaxLength; + int mNavableRects; // FIXME: could be bitfield once I limit max number of rects + int mParentIndex; + int mTextSize; + int mTabIndex; + mutable Condition mCondition : 5; // why the node was not chosen on the first pass + CachedNodeType mType : 3; + bool mClippedOut : 1; + bool mDisabled : 1; + bool mFixedUpFocusRects : 1; + bool mHasFocusRing : 1; + bool mHasMouseOver : 1; + bool mIsAnchor : 1; + bool mIsArea : 1; + bool mIsFocus : 1; + bool mIsInput : 1; + bool mIsParentAnchor : 1; + bool mIsPassword : 1; + bool mIsRtlText : 1; + bool mIsTextArea : 1; + bool mIsTextField : 1; + bool mIsTransparent : 1; + bool mIsUnclipped : 1; + bool mLast : 1; // true if this is the last node in a group + bool mWantsKeyEvents : 1; // true for nodes like plugins +#ifdef BROWSER_DEBUG +public: + WebCore::Node* webCoreNode() const { return (WebCore::Node*) mNode; } + bool mDisplayMeasure; + mutable bool mInCompare; + // mutable int mCondition; + int mSideDistance; + int mSecondSide; +#endif +#if DEBUG_NAV_UI || DUMP_NAV_CACHE +public: + class Debug { +public: + CachedNode* base() const; + const char* condition(Condition t) const; + void print() const; + const char* type(CachedNodeType t) const; +#if DUMP_NAV_CACHE + int mNodeIndex; + int mParentGroupIndex; +#endif + } mDebug; + friend class CachedNode::Debug; +#endif +}; + +} + +#endif diff --git a/WebKit/android/nav/CachedNodeType.h b/WebKit/android/nav/CachedNodeType.h new file mode 100644 index 0000000..07346ac --- /dev/null +++ b/WebKit/android/nav/CachedNodeType.h @@ -0,0 +1,41 @@ +/* + * 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef CachedNodeType_H +#define CachedNodeType_H + +namespace android { + +enum CachedNodeType { + NORMAL_CACHEDNODETYPE = 0, + ADDRESS_CACHEDNODETYPE = 1, + EMAIL_CACHEDNODETYPE = 2, + PHONE_CACHEDNODETYPE = 4, + ALL_CACHEDNODETYPES = 7 +}; + +} + +#endif diff --git a/WebKit/android/nav/CachedPrefix.h b/WebKit/android/nav/CachedPrefix.h new file mode 100644 index 0000000..b466771 --- /dev/null +++ b/WebKit/android/nav/CachedPrefix.h @@ -0,0 +1,46 @@ +/* + * 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef CachedPrefix_H +#define CachedPrefix_H + +#ifndef LOG_TAG +#define LOG_TAG "navcache" +#endif + +#include "config.h" +#include "CachedDebug.h" + +#ifndef _LIBS_CUTILS_LOG_H + #ifdef LOG + #undef LOG + #endif + + #include <utils/Log.h> +#endif + +#define OFFSETOF(type, field) ((char*)&(((type*)1)->field) - (char*)1) // avoids gnu warning + +#endif diff --git a/WebKit/android/nav/CachedRoot.cpp b/WebKit/android/nav/CachedRoot.cpp new file mode 100644 index 0000000..4a50c80 --- /dev/null +++ b/WebKit/android/nav/CachedRoot.cpp @@ -0,0 +1,1087 @@ +/* + * 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 APPLE COMPUTER, INC. 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 "SkBitmap.h" +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkPixelRef.h" +#include "SkRegion.h" + +#include "CachedRoot.h" + +#ifdef DUMP_NAV_CACHE_USING_PRINTF + extern android::Mutex gWriteLogMutex; +#endif + +namespace android { + +class CommonCheck : public SkBounder { +public: + enum Type { + kNo_Type, + kDrawBitmap_Type, + kDrawGlyph_Type, + kDrawPaint_Type, + kDrawPath_Type, + kDrawPicture_Type, + kDrawPoints_Type, + kDrawPosText_Type, + kDrawPosTextH_Type, + kDrawRect_Type, + kDrawSprite_Type, + kDrawText_Type, + kDrawTextOnPath_Type + }; + + static bool isTextType(Type t) { + return t == kDrawPosTextH_Type || t == kDrawText_Type; + } + + CommonCheck() : mType(kNo_Type), mAllOpaque(true), mIsOpaque(true) { + setEmpty(); + } + + bool doRect(Type type) { + mType = type; + return doIRect(mUnion); + } + + bool joinGlyphs(const SkIRect& rect) { + bool isGlyph = mType == kDrawGlyph_Type; + if (isGlyph) + mUnion.join(rect); + return isGlyph; + } + + void setAllOpaque(bool opaque) { mAllOpaque = opaque; } + void setEmpty() { mUnion.setEmpty(); } + void setIsOpaque(bool opaque) { mIsOpaque = opaque; } + void setType(Type type) { mType = type; } + + Type mType; + SkIRect mUnion; + bool mAllOpaque; + bool mIsOpaque; +}; + +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + static const char* TypeNames[] = { + "kNo_Type", + "kDrawBitmap_Type", + "kDrawGlyph_Type", + "kDrawPaint_Type", + "kDrawPath_Type", + "kDrawPicture_Type", + "kDrawPoints_Type", + "kDrawPosText_Type", + "kDrawPosTextH_Type", + "kDrawRect_Type", + "kDrawSprite_Type", + "kDrawText_Type", + "kDrawTextOnPath_Type" + }; +#endif + +#define kMargin 16 +#define kSlop 2 + +class BoundsCheck : public CommonCheck { +public: + BoundsCheck() { + mAllDrawnIn.setEmpty(); + mLastAll.setEmpty(); + mLastOver.setEmpty(); + } + + static int Area(SkIRect test) { + return test.width() * test.height(); + } + + void checkLast() { + if (mAllDrawnIn.isEmpty()) + return; + if (mLastAll.isEmpty() || Area(mLastAll) < Area(mAllDrawnIn)) { + mLastAll = mAllDrawnIn; + mDrawnOver.setEmpty(); + } + mAllDrawnIn.setEmpty(); + } + + bool hidden() { + return (mLastAll.isEmpty() && mLastOver.isEmpty()) || + mDrawnOver.contains(mBounds); + } + + virtual bool onIRect(const SkIRect& rect) { + if (joinGlyphs(rect)) + return false; + bool interestingType = mType == kDrawBitmap_Type || + mType == kDrawRect_Type || isTextType(mType); + if (SkIRect::Intersects(mBounds, rect) == false) { +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + LOGD("%s (no intersect) rect={%d,%d,%d,%d} mType=%s\n", __FUNCTION__, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + TypeNames[mType]); +#endif + if (interestingType) + checkLast(); + return false; + } + if (interestingType == false) + return false; + if (mBoundsSlop.contains(rect) || + (mBounds.fLeft == rect.fLeft && mBounds.fRight == rect.fRight && + mBounds.fTop >= rect.fTop && mBounds.fBottom <= rect.fBottom) || + (mBounds.fTop == rect.fTop && mBounds.fBottom == rect.fBottom && + mBounds.fLeft >= rect.fLeft && mBounds.fRight <= rect.fRight)) { + mDrawnOver.setEmpty(); + mAllDrawnIn.join(rect); +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + LOGD("%s (contains) rect={%d,%d,%d,%d}" + " mAllDrawnIn={%d,%d,%d,%d} mType=%s\n", __FUNCTION__, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + mAllDrawnIn.fLeft, mAllDrawnIn.fTop, mAllDrawnIn.fRight, mAllDrawnIn.fBottom, + TypeNames[mType]); +#endif + } else { + checkLast(); + if (!isTextType(mType)) { + if ( +#if 0 +// should the opaqueness of the bitmap disallow its ability to draw over? +// not sure that this test is needed + (mType != kDrawBitmap_Type || + (mIsOpaque && mAllOpaque)) && +#endif + mLastAll.isEmpty() == false) + mDrawnOver.op(rect, SkRegion::kUnion_Op); + } else { +// FIXME +// sometimes the text is not drawn entirely inside the focus area, even though +// it is the correct text. Until I figure out why, I allow text drawn at the +// end that is not covered up by something else to represent the focusable link +// example that triggers this that should be figured out: +// http://cdn.labpixies.com/campaigns/blackjack/blackjack.html?lang=en&country=US&libs=assets/feature/core +// ( http://tinyurl.com/ywsyzb ) + mLastOver = rect; + } +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + const SkIRect& drawnOver = mDrawnOver.getBounds(); + LOGD("%s (overlaps) rect={%d,%d,%d,%d}" + " mDrawnOver={%d,%d,%d,%d} mType=%s mIsOpaque=%s mAllOpaque=%s\n", __FUNCTION__, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + drawnOver.fLeft, drawnOver.fTop, drawnOver.fRight, drawnOver.fBottom, + TypeNames[mType], mIsOpaque ? "true" : "false", mAllOpaque ? "true" : "false"); +#endif + } + return false; + } + + SkIRect mBounds; + SkIRect mBoundsSlop; + SkRegion mDrawnOver; + SkIRect mLastOver; + SkIRect mAllDrawnIn; + SkIRect mLastAll; +}; + +class BoundsCanvas : public SkCanvas { +public: + + BoundsCanvas(CommonCheck* bounder) : mBounder(*bounder) { + mTransparentLayer = 0; + setBounder(bounder); + } + + virtual ~BoundsCanvas() { + setBounder(NULL); + } + + virtual void drawPaint(const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPaint_Type); + SkCanvas::drawPaint(paint); + } + + virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPoints_Type); + SkCanvas::drawPoints(mode, count, pts, paint); + } + + virtual void drawRect(const SkRect& rect, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawRect_Type); + SkCanvas::drawRect(rect, paint); + } + + virtual void drawPath(const SkPath& path, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawPath_Type); + SkCanvas::drawPath(path, paint); + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, + const SkMatrix& matrix, const SkPaint& paint) { + mBounder.setType(CommonCheck::kDrawBitmap_Type); + mBounder.setIsOpaque(bitmap.isOpaque()); + SkCanvas::commonDrawBitmap(bitmap, matrix, paint); + } + + virtual void drawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint = NULL) { + mBounder.setType(CommonCheck::kDrawSprite_Type); + mBounder.setIsOpaque(bitmap.isOpaque()); + SkCanvas::drawSprite(bitmap, left, top, paint); + } + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + SkCanvas::drawText(text, byteLength, x, y, paint); + mBounder.doRect(CommonCheck::kDrawText_Type); + } + + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + SkCanvas::drawPosText(text, byteLength, pos, paint); + mBounder.doRect(CommonCheck::kDrawPosText_Type); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + SkCanvas::drawPosTextH(text, byteLength, xpos, constY, paint); + if (mBounder.mUnion.isEmpty()) + return; + SkPaint::FontMetrics metrics; + paint.getFontMetrics(&metrics); + SkPoint upDown[2] = { {xpos[0], constY + metrics.fAscent}, + {xpos[0], constY + metrics.fDescent} }; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapPoints(upDown, 2); + if (upDown[0].fX == upDown[1].fX) { + mBounder.mUnion.fTop = SkScalarFloor(upDown[0].fY); + mBounder.mUnion.fBottom = SkScalarFloor(upDown[1].fY); + } + mBounder.doRect(CommonCheck::kDrawPosTextH_Type); + } + + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + mBounder.setEmpty(); + mBounder.setType(CommonCheck::kDrawGlyph_Type); + SkCanvas::drawTextOnPath(text, byteLength, path, matrix, paint); + mBounder.doRect(CommonCheck::kDrawTextOnPath_Type); + } + + virtual void drawPicture(SkPicture& picture) { + mBounder.setType(CommonCheck::kDrawPicture_Type); + SkCanvas::drawPicture(picture); + } + + virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, + SaveFlags flags) { + int depth = SkCanvas::saveLayer(bounds, paint, flags); + if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) { + mTransparentLayer = depth; + mBounder.setAllOpaque(false); + } + return depth; + } + + virtual void restore() { + int depth = getSaveCount(); + if (depth == mTransparentLayer) { + mTransparentLayer = 0; + mBounder.setAllOpaque(true); + } + SkCanvas::restore(); + } + + int mTransparentLayer; + CommonCheck& mBounder; +}; + +/* +CenterCheck examines the text in a picture, within a viewable rectangle, +and returns via center() the optimal amount to scroll in x to display the +paragraph of text. + +The caller of CenterCheck has configured (but not allocated) a bitmap +the height and three times the width of the view. The picture is drawn centered +in the bitmap, so text that would be revealed, if the view was scrolled up to +a view-width to the left or right, is considered. +*/ +class CenterCheck : public CommonCheck { +public: + CenterCheck(int x, int y, int width) : mX(x), mY(y), + mHitLeft(x), mHitRight(x), mMostLeft(INT_MAX), mMostRight(-INT_MAX), + mViewLeft(width), mViewRight(width << 1) { + mHit.set(x - CENTER_SLOP, y - CENTER_SLOP, + x + CENTER_SLOP, y + CENTER_SLOP); + mPartial.setEmpty(); + } + + int center() { + doRect(); // process the final line of text + /* If the touch coordinates aren't near any text, return 0 */ + if (mHitLeft == mHitRight) { + DBG_NAV_LOGD("abort: mHitLeft=%d ==mHitRight", mHitLeft); + return 0; + } + int leftOver = mHitLeft - mViewLeft; + int rightOver = mHitRight - mViewRight; + int center; + /* If the touched text is too large to entirely fit on the screen, + center it. */ + if (leftOver < 0 && rightOver > 0) { + center = (leftOver + rightOver) >> 1; + DBG_NAV_LOGD("overlap: leftOver=%d rightOver=%d center=%d", + leftOver, rightOver, center); + return center; + } + center = (mMostLeft + mMostRight) >> 1; // the paragraph center + if (leftOver > 0 && rightOver >= 0) { // off to the right + if (center > mMostLeft) // move to center loses left-most text? + center = mMostLeft; + } else if (rightOver < 0 && leftOver <= 0) { // off to the left + if (center < mMostRight) // move to center loses right-most text? + center = mMostRight; + } else { +#ifdef DONT_CENTER_IF_ALREADY_VISIBLE + center = 0; // paragraph is already fully visible +#endif + } + DBG_NAV_LOGD("scroll: leftOver=%d rightOver=%d center=%d", + leftOver, rightOver, center); + return center; + } + +protected: + virtual bool onIRect(const SkIRect& rect) { + if (joinGlyphs(rect)) // assembles glyphs into a text string + return false; + if (!isTextType(mType)) + return false; + /* Text on one line may be broken into several parts. Reassemble + the text into a rectangle before considering it. */ + if (rect.fTop < mPartial.fBottom && rect.fBottom > + mPartial.fTop && mPartial.fRight + CENTER_SLOP >= rect.fLeft) { + DBG_NAV_LOGD("join mPartial=(%d, %d, %d, %d) rect=(%d, %d, %d, %d)", + mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + mPartial.join(rect); + return false; + } + if (mPartial.isEmpty() == false) + doRect(); // process the previous line of text + mPartial = rect; + return false; + } + + void doRect() + { + /* Record the outer bounds of the lines of text that was 'hit' by the + touch coordinates, given some slop */ + if (SkIRect::Intersects(mPartial, mHit)) { + if (mHitLeft > mPartial.fLeft) + mHitLeft = mPartial.fLeft; + if (mHitRight < mPartial.fRight) + mHitRight = mPartial.fRight; + DBG_NAV_LOGD("mHitLeft=%d mHitRight=%d", mHitLeft, mHitRight); + } + /* If the considered text is completely to the left or right of the + touch coordinates, skip it */ + if (mPartial.fLeft > mX || mPartial.fRight < mX) + return; + int leftOver = mPartial.fLeft - mViewLeft; + int rightOver = mPartial.fRight - mViewRight; + /* If leftOver <= 0, the text starts off the screen. + If rightOver >= 0, the text ends off the screen. + */ + if (leftOver <= 0 && rightOver >= 0) // discard wider than screen + return; +#ifdef DONT_CENTER_IF_ALREADY_VISIBLE + if (leftOver > 0 && rightOver < 0) // discard already visible + return; +#endif + /* record the smallest margins on the left and right */ + if (mMostLeft > leftOver) + mMostLeft = leftOver; + if (mMostRight < rightOver) + mMostRight = rightOver; + DBG_NAV_LOGD("leftOver=%d rightOver=%d mMostLeft=%d mMostRight=%d", + leftOver, rightOver, mMostLeft, mMostRight); + } + + static const int CENTER_SLOP = 10; // space between text parts and lines + /* const */ SkIRect mHit; // sloppy hit rectangle + SkIRect mPartial; // accumulated text bounds, per line + const int mX; // touch location + const int mY; + int mHitLeft; // touched text extremes + int mHitRight; + int mMostLeft; // paragraph extremes + int mMostRight; + const int mViewLeft; // middle third of 3x-wide view + const int mViewRight; +}; + +class ImageCanvas : public SkCanvas { +public: + ImageCanvas(SkBounder* bounder) : mURI(NULL) { + setBounder(bounder); + } + +// Currently webkit's bitmap draws always seem to be cull'd before this entry +// point is called, so we assume that any bitmap that gets here is inside our +// tiny clip (may not be true in the future) + virtual void commonDrawBitmap(const SkBitmap& bitmap, + const SkMatrix& , const SkPaint& ) { + SkPixelRef* pixelRef = bitmap.pixelRef(); + if (pixelRef != NULL) { + mURI = pixelRef->getURI(); + } + } + + const char* mURI; +}; + +class ImageCheck : public SkBounder { +public: + virtual bool onIRect(const SkIRect& rect) { + return false; + } +}; + +class JiggleCheck : public CommonCheck { +public: + JiggleCheck(int delta, int width) : mDelta(delta), mMaxX(width) { + mMaxJiggle = 0; + mMinX = mMinJiggle = abs(delta); + mMaxWidth = width + mMinX; + } + + int jiggle() { + if (mMinJiggle > mMaxJiggle) + return mDelta; + int avg = (mMinJiggle + mMaxJiggle + 1) >> 1; + return mDelta < 0 ? -avg : avg; + } + + virtual bool onIRect(const SkIRect& rect) { + if (joinGlyphs(rect)) + return false; + if (mType != kDrawBitmap_Type && !isTextType(mType)) + return false; + int min, max; + if (mDelta < 0) { + min = mMinX - rect.fLeft; + max = mMaxWidth - rect.fRight; + } else { + min = rect.fRight - mMaxX; + max = rect.fLeft; + } + if (min <= 0) + return false; + if (max >= mMinX) + return false; + if (mMinJiggle > min) + mMinJiggle = min; + if (mMaxJiggle < max) + mMaxJiggle = max; + return false; + } + + int mDelta; + int mMaxJiggle; + int mMaxX; + int mMinJiggle; + int mMinX; + int mMaxWidth; +}; + +bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction direction, + WebCore::IntPoint* scrollPtr, bool findClosest) +{ + WebCore::IntRect newOutset; + const CachedNode* newNode = best->mNode; + // see if there's a middle node + // if the middle node is in the visited list, + // or if none was computed and the newNode is in the visited list, + // treat result as NULL + if (newNode != NULL && findClosest) { + if (best->bounds().intersects(mHistory->mPriorBounds) == false && + checkBetween(best, direction)) + newNode = best->mNode; + if (findClosest && maskIfHidden(best)) { + innerMove(document(), best, direction, scrollPtr, false); + return true; + } + newNode->focusRingBounds(&newOutset); + } + int delta; + bool newNodeInView = scrollDelta(newOutset, direction, &delta); + if (delta && scrollPtr && (newNode == NULL || newNodeInView == false || + (best->mNavOutside && best->mWorkingOutside))) + *scrollPtr = WebCore::IntPoint(direction & UP_DOWN ? 0 : delta, + direction & UP_DOWN ? delta : 0); + return false; +} + + +int CachedRoot::checkForCenter(int x, int y) const +{ + int width = mViewBounds.width(); + CenterCheck centerCheck(x + width - mViewBounds.x(), y - mViewBounds.y(), + width); + BoundsCanvas checker(¢erCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width * 3, + mViewBounds.height()); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(width - mViewBounds.x()), + SkIntToScalar(-mViewBounds.y())); + checker.drawPicture(*mPicture); + return centerCheck.center(); +} + +void CachedRoot::checkForJiggle(int* xDeltaPtr) const +{ + int xDelta = *xDeltaPtr; + JiggleCheck jiggleCheck(xDelta, mViewBounds.width()); + BoundsCanvas checker(&jiggleCheck); + SkBitmap bitmap; + int absDelta = abs(xDelta); + bitmap.setConfig(SkBitmap::kARGB_8888_Config, mViewBounds.width() + + absDelta, mViewBounds.height()); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(-mViewBounds.x() - + (xDelta < 0 ? xDelta : 0)), SkIntToScalar(-mViewBounds.y())); + checker.drawPicture(*mPicture); + *xDeltaPtr = jiggleCheck.jiggle(); +} + +const CachedNode* CachedRoot::findAt(const WebCore::IntRect& rect, + const CachedFrame** framePtr, int* x, int* y) const +{ + int best = INT_MAX; + (const_cast<CachedRoot*>(this))->resetClippedOut(); + const CachedNode* directHit = NULL; + const CachedNode* node = findBestAt(rect, &best, &directHit, framePtr, x, y); + DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(), + node == NULL ? NULL : node->nodePointer()); + if (node == NULL) { + node = findBestHitAt(rect, &best, framePtr, x, y); + DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(), + node == NULL ? NULL : node->nodePointer()); + } + if (node == NULL) { + *framePtr = findBestFrameAt(rect.x() + (rect.width() >> 1), + rect.y() + (rect.height() >> 1)); + } + return node; +} + +WebCore::IntPoint CachedRoot::focusLocation() const +{ + const WebCore::IntRect& bounds = mHistory->mNavBounds; + return WebCore::IntPoint(bounds.x() + (bounds.width() >> 1), + bounds.y() + (bounds.height() >> 1)); +} + +// These reset the values because we only want to get the selection the first time. +// After that, the selection is no longer accurate. +int CachedRoot::getAndResetSelectionEnd() +{ + int end = mSelectionEnd; + mSelectionEnd = -1; + return end; +} + +int CachedRoot::getAndResetSelectionStart() +{ + int start = mSelectionStart; + mSelectionStart = -1; + return start; +} + +void CachedRoot::getSimulatedMousePosition(WebCore::IntPoint* point) +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + const WebCore::IntRect& mouseBounds = mHistory->mMouseBounds; + point->setX(mouseBounds.x() + (mouseBounds.width() >> 1)); + point->setY(mouseBounds.y() + (mouseBounds.height() >> 1)); +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + const WebCore::IntRect& navBounds = mHistory->mNavBounds; + LOGD("%s mHistory->mNavBounds={%d,%d,%d,%d} " + "mHistory->mMouseBounds={%d,%d,%d,%d} point={%d,%d}\n", __FUNCTION__, + navBounds.x(), navBounds.y(), navBounds.width(), navBounds.height(), + mouseBounds.x(), mouseBounds.y(), mouseBounds.width(), mouseBounds.height(), + point->x(), point->y()); +#endif +} + +void CachedRoot::init(WebCore::Frame* frame, CachedHistory* history) +{ + CachedFrame::init(this, -1, frame); + reset(); + mHistory = history; + mPicture = NULL; +} + +bool CachedRoot::innerDown(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingVertical() >= mViewBounds.x()); + ASSERT(maxWorkingVertical() <= mViewBounds.right()); + setupScrolledBounds(); + // (line up) + mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); + int testTop = mScrolledBounds.y(); + int viewBottom = mViewBounds.bottom(); + if (mFocusBounds.isEmpty() == false && + mFocusBounds.bottom() > viewBottom && viewBottom < mContents.height()) + return false; + if (mHistory->mNavBounds.isEmpty() == false) { + int navTop = mHistory->mNavBounds.y(); + int scrollBottom; + if (testTop < navTop && navTop < (scrollBottom = mScrolledBounds.bottom())) { + mScrolledBounds.setHeight(scrollBottom - navTop); + mScrolledBounds.setY(navTop); + } + } + frameDown(test, NULL, bestData, currentFocus()); + return true; +} + +bool CachedRoot::innerLeft(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingHorizontal() >= mViewBounds.y()); + ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom()); + setupScrolledBounds(); + mScrolledBounds.setX(mScrolledBounds.x() - mMaxXScroll); + mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); + int testRight = mScrolledBounds.right(); + int viewLeft = mViewBounds.x(); + if (mFocusBounds.isEmpty() == false && + mFocusBounds.x() < viewLeft && viewLeft > mContents.x()) + return false; + if (mHistory->mNavBounds.isEmpty() == false) { + int navRight = mHistory->mNavBounds.right(); + int scrollLeft; + if (testRight > navRight && navRight > (scrollLeft = mScrolledBounds.x())) + mScrolledBounds.setWidth(navRight - scrollLeft); + } + frameLeft(test, NULL, bestData, currentFocus()); + return true; +} + + +void CachedRoot::innerMove(const CachedNode* node, BestData* bestData, + Direction direction, WebCore::IntPoint* scroll, bool firstCall) +{ + bestData->reset(); + mFocusChild = false; + bool outOfFocus = mFocus < 0; + bool firstTime = mHistory->didFirstLayout() && outOfFocus; +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + LOGD("%s mHistory->didFirstLayout()=%s && mFocus=%d\n", __FUNCTION__, + mHistory->didFirstLayout() ? "true" : "false", mFocus); +#endif + if (firstTime) + mHistory->reset(); + const CachedNode* focus = currentFocus(); + mHistory->setWorking(direction, focus, mViewBounds); + mFocusBounds = WebCore::IntRect(0, 0, 0, 0); + if (focus != NULL) + focus->getBounds(&mFocusBounds); + bool findClosest = false; + if (mScrollOnly == false) { + switch (direction) { + case LEFT: + if (outOfFocus) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.right(), + mViewBounds.y(), 1, mViewBounds.height()); + findClosest = innerLeft(node, bestData); + break; + case RIGHT: + if (outOfFocus) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x() - 1, + mViewBounds.y(), 1, mViewBounds.height()); + findClosest = innerRight(node, bestData); + break; + case UP: + if (outOfFocus) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), + mViewBounds.bottom(), mViewBounds.width(), 1); + findClosest = innerUp(node, bestData); + break; + case DOWN: + if (outOfFocus) + mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), + mViewBounds.y() - 1, mViewBounds.width(), 1); + findClosest = innerDown(node, bestData); + break; + case UNINITIALIZED: + default: + ASSERT(0); + } + } + if (firstCall) + mHistory->mPriorBounds = mHistory->mNavBounds; // bounds always advances, even if new node is ultimately NULL + bestData->mMouseBounds = bestData->mNodeBounds; + if (adjustForScroll(bestData, direction, scroll, findClosest)) + return; + if (bestData->mNode != NULL) { + mHistory->addToVisited(bestData->mNode, direction); + mHistory->mNavBounds = mFocusBounds = bestData->mNodeBounds; + mHistory->mMouseBounds = bestData->mMouseBounds; + } else if (scroll->x() != 0 || scroll->y() != 0) { + WebCore::IntRect newBounds = mHistory->mNavBounds; + int offsetX = scroll->x(); + int offsetY = scroll->y(); + newBounds.move(offsetX, offsetY); + if (mViewBounds.x() > newBounds.x()) + offsetX = mViewBounds.x() - mHistory->mNavBounds.x(); + else if (mViewBounds.right() < newBounds.right()) + offsetX = mViewBounds.right() - mHistory->mNavBounds.right(); + if (mViewBounds.y() > newBounds.y()) + offsetY = mViewBounds.y() - mHistory->mNavBounds.y(); + else if (mViewBounds.bottom() < newBounds.bottom()) + offsetY = mViewBounds.bottom() - mHistory->mNavBounds.bottom(); + mHistory->mNavBounds.move(offsetX, offsetY); + } + mHistory->setDidFirstLayout(false); +} + +bool CachedRoot::innerRight(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingHorizontal() >= mViewBounds.y()); + ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom()); + setupScrolledBounds(); + // (align) + mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); + int testLeft = mScrolledBounds.x(); + int viewRight = mViewBounds.right(); + if (mFocusBounds.isEmpty() == false && + mFocusBounds.right() > viewRight && viewRight < mContents.width()) + return false; + if (mHistory->mNavBounds.isEmpty() == false) { + int navLeft = mHistory->mNavBounds.x(); + int scrollRight; + if (testLeft < navLeft && navLeft < (scrollRight = mScrolledBounds.right())) { + mScrolledBounds.setWidth(scrollRight - navLeft); + mScrolledBounds.setX(navLeft); + } + } + frameRight(test, NULL, bestData, currentFocus()); + return true; +} + +bool CachedRoot::innerUp(const CachedNode* test, BestData* bestData) const +{ + ASSERT(minWorkingVertical() >= mViewBounds.x()); + ASSERT(maxWorkingVertical() <= mViewBounds.right()); + setupScrolledBounds(); + mScrolledBounds.setY(mScrolledBounds.y() - mMaxYScroll); + mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); + int testBottom = mScrolledBounds.bottom(); + int viewTop = mViewBounds.y(); + if (mFocusBounds.isEmpty() == false && + mFocusBounds.y() < viewTop && viewTop > mContents.y()) + return false; + if (mHistory->mNavBounds.isEmpty() == false) { + int navBottom = mHistory->mNavBounds.bottom(); + int scrollTop; + if (testBottom > navBottom && navBottom > (scrollTop = mScrolledBounds.y())) + mScrolledBounds.setHeight(navBottom - scrollTop); + } + frameUp(test, NULL, bestData, currentFocus()); + return true; +} + +WebCore::String CachedRoot::imageURI(int x, int y) const +{ + ImageCheck imageCheck; + ImageCanvas checker(&imageCheck); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1); + checker.setBitmapDevice(bitmap); + checker.translate(SkIntToScalar(-x), SkIntToScalar(-y)); + checker.drawPicture(*mPicture); + return WebCore::String(checker.mURI); +} + +bool CachedRoot::maskIfHidden(BestData* best) const +{ + if (mPicture == NULL) { +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + LOGD("%s missing picture\n", __FUNCTION__); +#endif + return false; + } + const CachedNode* bestNode = best->mNode; + if (bestNode->isUnclipped()) + return false; + // given the picture matching this nav cache + // create an SkBitmap with dimensions of the focus intersected w/ extended view + const WebCore::IntRect& nodeBounds = bestNode->getBounds(); + 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); + 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); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, marginBounds.width(), + marginBounds.height()); + checker.setBitmapDevice(bitmap); + // insert probes to be called when the data corresponding to this focus ring is drawn + // need to know if focus ring was generated by text, image, or parent (like div) + // ? need to know (like imdb menu bar) to give up sometimes (when?) + checker.translate(SkIntToScalar(leftMargin - bounds.x()), + SkIntToScalar(topMargin - bounds.y())); + checker.drawPicture(*mPicture); + boundsCheck.checkLast(); + // was it not drawn or clipped out? + if (boundsCheck.hidden()) { // if hidden, return false so that nav can try again + CachedNode* node = const_cast<CachedNode*>(best->mNode); +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + const SkIRect& m = boundsCheck.mBounds; + const SkIRect& s = boundsCheck.mBoundsSlop; + LOGD("%s hidden node:%p (%d) mBounds={%d,%d,%d,%d} mBoundsSlop=" + "{%d,%d,%d,%d}\n", __FUNCTION__, 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; + LOGD("%s hidden mDrawnOver={%d,%d,%d,%d} mLastAll={%d,%d,%d,%d}" + " mUnion={%d,%d,%d,%d}\n", __FUNCTION__, + 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; + LOGD("%s hidden mAllDrawnIn={%d,%d,%d,%d} mScrolledBounds={%d,%d,%d,%d}" + " nodeBounds={%d,%d,%d,%d}\n", __FUNCTION__, + a.fLeft, a.fTop, a.fRight, a.fBottom, + c.x(), c.y(), c.right(), c.bottom(), + b.x(), b.y(), b.right(), b.bottom()); + LOGD("%s bits.mWidth=%d bits.mHeight=%d transX=%d transY=%d\n", __FUNCTION__, + marginBounds.width(),marginBounds.height(), + kMargin - bounds.x(), kMargin - bounds.y()); +#endif + node->setDisabled(true); + node->setClippedOut(unclipped == false); + 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 && !defined BROWSER_DEBUG + 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 && !defined BROWSER_DEBUG + const SkIRect& modded = boundsCheck.mBounds; + LOGD("%s partially occluded node:%p (%d) old:{%d,%d,%d,%d} new:{%d,%d,%d,%d}\n", + __FUNCTION__, best->mNode, best->mNode->index(), + orig.fLeft, orig.fTop, orig.fRight, orig.fBottom, + base.fLeft, base.fTop, base.fRight, base.fBottom); +#endif + best->mMouseBounds = WebCore::IntRect(bounds.x() + base.fLeft - kMargin, + bounds.y() + base.fTop - kMargin, base.width(), base.height()); + } + return false; +} + +const CachedNode* CachedRoot::moveFocus(Direction direction, const CachedFrame** framePtr, + WebCore::IntPoint* scroll) +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + CachedRoot* frame = this; + const CachedNode* node = frame->document(); + if (node == NULL) + return NULL; + if (mViewBounds.isEmpty()) + return NULL; + resetClippedOut(); + setData(); + BestData bestData; + innerMove(node, &bestData, direction, scroll, true); + *framePtr = bestData.mFrame; + return const_cast<CachedNode*>(bestData.mNode); +} + +void CachedRoot::reset() +{ +#ifndef NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif + mContents = mViewBounds = WebCore::IntRect(0, 0, 0, 0); + mMaxXScroll = mMaxYScroll = 0; + mSelectionStart = mSelectionEnd = -1; + mScrollOnly = false; + mFocusBounds = WebCore::IntRect(0, 0, 0, 0); +} + +bool CachedRoot::scrollDelta(WebCore::IntRect& newOutset, Direction direction, int* delta) +{ + switch (direction) { + case LEFT: + *delta = -mMaxXScroll; + return newOutset.x() >= mViewBounds.x(); + case RIGHT: + *delta = mMaxXScroll; + return newOutset.right() <= mViewBounds.right(); + case UP: + *delta = -mMaxYScroll; + return newOutset.y() >= mViewBounds.y(); + case DOWN: + *delta = mMaxYScroll; + return newOutset.bottom() <= mViewBounds.bottom(); + default: + *delta = 0; + ASSERT(0); + } + return false; +} + +void CachedRoot::setCachedFocus(CachedFrame* frame, CachedNode* node) +{ +#if !defined NDEBUG + ASSERT(CachedFrame::mDebug.mInUse); +#endif +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + const CachedNode* focus = currentFocus(); + WebCore::IntRect bounds; + if (focus) + bounds = focus->bounds(); + LOGD("%s old focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}\n", __FUNCTION__, + focus ? focus->index() : 0, + focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif + clearFocus(); + if (node == NULL) + return; + node->setIsFocus(true); + ASSERT(node->isFrame() == false); + frame->setFocusIndex(node - frame->document()); + ASSERT(frame->focusIndex() > 0 && frame->focusIndex() < (int) frame->size()); + CachedFrame* parent; + while ((parent = frame->parent()) != NULL) { + parent->setFocusIndex(frame->indexInParent()); + frame = parent; + } +#if DEBUG_NAV_UI && !defined BROWSER_DEBUG + focus = currentFocus(); + bounds = WebCore::IntRect(0, 0, 0, 0); + if (focus) + bounds = focus->bounds(); + LOGD("%s new focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}\n", __FUNCTION__, + focus ? focus->index() : 0, + focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); +#endif +} + +void CachedRoot::setupScrolledBounds() const +{ + mScrolledBounds = mViewBounds; +} + +#if DUMP_NAV_CACHE + +#define DEBUG_PRINT_BOOL(field) \ + DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false") + +#define DEBUG_PRINT_RECT(field) \ + { const WebCore::IntRect& r = b->field; \ + DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \ + r.x(), r.y(), r.width(), r.height()); } + +CachedRoot* CachedRoot::Debug::base() const { + CachedRoot* nav = (CachedRoot*) ((char*) this - OFFSETOF(CachedRoot, mDebug)); + return nav; +} + +void CachedRoot::Debug::print() const +{ +#ifdef DUMP_NAV_CACHE_USING_PRINTF + gWriteLogMutex.lock(); + ASSERT(gNavCacheLogFile == NULL); + gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a"); +#endif + CachedRoot* b = base(); + b->CachedFrame::mDebug.print(); + b->mHistory->mDebug.print(b); + DEBUG_PRINT_RECT(mFocusBounds); + DUMP_NAV_LOGD("// int mMaxXScroll=%d, mMaxYScroll=%d;\n", + b->mMaxXScroll, b->mMaxYScroll); + DEBUG_PRINT_BOOL(mFocusChild); +#ifdef DUMP_NAV_CACHE_USING_PRINTF + if (gNavCacheLogFile) + fclose(gNavCacheLogFile); + gNavCacheLogFile = NULL; + gWriteLogMutex.unlock(); +#endif +} + +#endif + +} diff --git a/WebKit/android/nav/CachedRoot.h b/WebKit/android/nav/CachedRoot.h new file mode 100644 index 0000000..ab1b823 --- /dev/null +++ b/WebKit/android/nav/CachedRoot.h @@ -0,0 +1,115 @@ +/* + * 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef CachedRoot_H +#define CachedRoot_H + +#include "CachedFrame.h" +#include "IntPoint.h" +#include "SkPicture.h" + +class SkRect; + +namespace android { + +class CachedHistory; +class CachedNode; + +class CachedRoot : public CachedFrame { +public: + bool adjustForScroll(BestData* , Direction , WebCore::IntPoint* scrollPtr, + bool findClosest); + int checkForCenter(int x, int y) const; + void checkForJiggle(int* ) const; + int documentHeight() { return mContents.height(); } + int documentWidth() { return mContents.width(); } + const CachedNode* findAt(const WebCore::IntRect& , const CachedFrame** , + int* x, int* y) const; + const WebCore::IntRect& focusBounds() const { return mFocusBounds; } + bool focusChild() const { return mFocusChild; } + WebCore::IntPoint focusLocation() const; + int generation() const { return mGeneration; } + SkPicture* getPicture() { return mPicture; } + int getAndResetSelectionEnd(); + int getAndResetSelectionStart(); +// const WebCore::IntRect& navClipBounds() const { return mClippedBounds; } + void getSimulatedMousePosition(WebCore::IntPoint* ); +// bool hasNavClipBounds() { return mClippedBounds.isEmpty() == false; } + void init(WebCore::Frame* , CachedHistory* ); + bool innerDown(const CachedNode* , BestData* ) const; + bool innerLeft(const CachedNode* , BestData* ) const; + void innerMove(const CachedNode* ,BestData* bestData, Direction , + WebCore::IntPoint* scroll, bool firstCall); + bool innerRight(const CachedNode* , BestData* ) const; + bool innerUp(const CachedNode* , BestData* ) const; + WebCore::String imageURI(int x, int y) const; + bool maskIfHidden(BestData* ) const; + const CachedNode* moveFocus(Direction , const CachedFrame** , WebCore::IntPoint* scroll); + void reset(); +// void resetNavClipBounds() { mClippedBounds = WebCore::IntRect(-1, -1, 0, 0); } + CachedHistory* rootHistory() const { return mHistory; } + bool scrollDelta(WebCore::IntRect& focusRingBounds, Direction , int* delta); + const WebCore::IntRect& scrolledBounds() const { return mScrolledBounds; } + void setCachedFocus(CachedFrame* , CachedNode* ); + void setFocusBounds(const WebCore::IntRect& r) { mFocusBounds = r; } + void setGeneration(int generation) { mGeneration = generation; } + void setTextGeneration(int textGeneration) { mTextGeneration = textGeneration; } + void setFocusChild(bool state) const { mFocusChild = state; } + void setMaxScroll(int x, int y) { mMaxXScroll = x; mMaxYScroll = y; } +// void setNavClipBounds(const WebCore::IntRect& r) { mClippedBounds = r; } + void setPicture(SkPicture* picture) { mPicture = picture; } + void setScrollOnly(bool state) { mScrollOnly = state; } + void setSelection(int start, int end) { mSelectionStart = start; mSelectionEnd = end; } + void setupScrolledBounds() const; + void setVisibleRect(const WebCore::IntRect& r) { mViewBounds = r; } + int textGeneration() const { return mTextGeneration; } + int width() const { return mPicture ? mPicture->width() : 0; } +private: + CachedHistory* mHistory; + SkPicture* mPicture; + WebCore::IntRect mFocusBounds; // chosen focus ring + mutable WebCore::IntRect mScrolledBounds; // view bounds + amount visible as result of scroll + int mGeneration; + int mTextGeneration; + int mMaxXScroll; + int mMaxYScroll; + // These two are ONLY used when the tree is rebuilt and the focus is a textfield/area + int mSelectionStart; + int mSelectionEnd; + mutable bool mFocusChild; // temporary state set if walked nodes are children of focus + bool mScrollOnly; +#if DUMP_NAV_CACHE +public: + class Debug { +public: + CachedRoot* base() const; + void print() const; + } mDebug; +#endif +}; + +} + +#endif diff --git a/WebKit/android/nav/FindCanvas.cpp b/WebKit/android/nav/FindCanvas.cpp new file mode 100644 index 0000000..24b0129 --- /dev/null +++ b/WebKit/android/nav/FindCanvas.cpp @@ -0,0 +1,467 @@ +/* + * Copyright 2008, 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 APPLE COMPUTER, INC. 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 "config.h" +#include "FindCanvas.h" + +#include "SkRect.h" + +// MatchInfo methods +//////////////////////////////////////////////////////////////////////////////// + +MatchInfo::MatchInfo() { + m_picture = 0; +} + +MatchInfo::~MatchInfo() { + m_picture->safeUnref(); +} + +MatchInfo::MatchInfo(const MatchInfo& src) { + m_location = src.m_location; + m_picture = src.m_picture; + m_picture->safeRef(); +} + +void MatchInfo::set(const SkRegion& region, SkPicture* pic) { + m_picture->safeUnref(); + m_location = region; + m_picture = pic; + SkASSERT(pic); + pic->ref(); +} + +// GlyphSet methods +//////////////////////////////////////////////////////////////////////////////// + +GlyphSet::GlyphSet(const SkPaint& paint, const UChar* lower, const UChar* upper, + size_t byteLength) { + SkPaint clonePaint(paint); + clonePaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + mTypeface = paint.getTypeface(); + mCount = clonePaint.textToGlyphs(lower, byteLength, NULL); + if (mCount > MAX_STORAGE_COUNT) { + mLowerGlyphs = new uint16_t[2*mCount]; + } else { + mLowerGlyphs = &mStorage[0]; + } + // Use one array, and have mUpperGlyphs point to a portion of it, + // so that we can reduce the number of new/deletes + mUpperGlyphs = mLowerGlyphs + mCount; + int count2 = clonePaint.textToGlyphs(lower, byteLength, mLowerGlyphs); + SkASSERT(mCount == count2); + count2 = clonePaint.textToGlyphs(upper, byteLength, mUpperGlyphs); + SkASSERT(mCount == count2); +} + +GlyphSet::~GlyphSet() { + // Do not need to delete mTypeface, which is not owned by us. + if (mCount > MAX_STORAGE_COUNT) { + delete[] mLowerGlyphs; + } // Otherwise, we just used local storage space, so no need to delete + // Also do not need to delete mUpperGlyphs, which simply points to a + // part of mLowerGlyphs +} + +GlyphSet::GlyphSet& GlyphSet::operator=(GlyphSet& src) { + mTypeface = src.mTypeface; + mCount = src.mCount; + if (mCount > MAX_STORAGE_COUNT) { + mLowerGlyphs = new uint16_t[2*mCount]; + } else { + mLowerGlyphs = &mStorage[0]; + } + memcpy(mLowerGlyphs, src.mLowerGlyphs, 2*mCount*sizeof(uint16_t)); + mUpperGlyphs = mLowerGlyphs + mCount; + return *this; +} + +bool GlyphSet::characterMatches(uint16_t c, int index) { + SkASSERT(index < mCount && index >= 0); + return c == mLowerGlyphs[index] || c == mUpperGlyphs[index]; +} + +// FindCanvas methods +//////////////////////////////////////////////////////////////////////////////// + +FindCanvas::FindCanvas(int width, int height, const UChar* lower, + const UChar* upper, size_t byteLength) + : mLowerText(lower) + , mUpperText(upper) + , mLength(byteLength) + , mNumFound(0) { + + setBounder(&mBounder); + mOutset = -SkIntToScalar(2); + mMatches = new WTF::Vector<MatchInfo>(); + mWorkingIndex = 0; + mWorkingCanvas = 0; + mWorkingPicture = 0; +} + +FindCanvas::~FindCanvas() { + setBounder(NULL); + /* Just in case getAndClear was not called. */ + delete mMatches; + mWorkingPicture->safeUnref(); +} + +// Each version of addMatch returns a rectangle for a match. +// Not all of the parameters are used by each version. +SkRect FindCanvas::addMatchNormal(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar pos[], SkScalar y) { + const uint16_t* lineStart = glyphs - index; + /* Use the original paint, since "text" is in glyphs */ + SkScalar before = paint.measureText(lineStart, index * sizeof(uint16_t), 0); + SkRect rect; + rect.fLeft = pos[0] + before; + int countInBytes = count * sizeof(uint16_t); + rect.fRight = paint.measureText(glyphs, countInBytes, 0) + rect.fLeft; + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + SkScalar baseline = y; + rect.fTop = baseline + fontMetrics.fAscent; + rect.fBottom = baseline + fontMetrics.fDescent; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapRect(&rect); + // Add the text to our picture. + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawText(glyphs, countInBytes, pos[0] + before, y, paint); + canvas->restoreToCount(saveCount); + return rect; +} + +SkRect FindCanvas::addMatchPos(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar xPos[], SkScalar /* y */) { + SkRect r; + r.setEmpty(); + const SkPoint* temp = reinterpret_cast<const SkPoint*> (xPos); + const SkPoint* points = &temp[index]; + int countInBytes = count * sizeof(uint16_t); + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + // Need to check each character individually, since the heights may be + // different. + for (int j = 0; j < count; j++) { + SkRect bounds; + bounds.fLeft = points[j].fX; + bounds.fRight = bounds.fLeft + + paint.measureText(&glyphs[j], sizeof(uint16_t), 0); + SkScalar baseline = points[j].fY; + bounds.fTop = baseline + fontMetrics.fAscent; + bounds.fBottom = baseline + fontMetrics.fDescent; + /* Accumulate and then add the resulting rect to mMatches */ + r.join(bounds); + } + SkMatrix matrix = getTotalMatrix(); + matrix.mapRect(&r); + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawPosText(glyphs, countInBytes, points, paint); + canvas->restoreToCount(saveCount); + return r; +} + +SkRect FindCanvas::addMatchPosH(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar position[], SkScalar constY) { + SkRect r; + // We only care about the positions starting at the index of our match + const SkScalar* xPos = &position[index]; + // This assumes that the position array is monotonic increasing + // The left bounds will be the position of the left most character + r.fLeft = xPos[0]; + // The right bounds will be the position of the last character plus its + // width + int lastIndex = count - 1; + r.fRight = paint.measureText(&glyphs[lastIndex], sizeof(uint16_t), 0) + + xPos[lastIndex]; + // Grab font metrics to determine the top and bottom of the bounds + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + r.fTop = constY + fontMetrics.fAscent; + r.fBottom = constY + fontMetrics.fDescent; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapRect(&r); + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawPosTextH(glyphs, count * sizeof(uint16_t), xPos, constY, paint); + canvas->restoreToCount(saveCount); + return r; +} + +void FindCanvas::drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + findHelper(text, byteLength, paint, &x, y, &FindCanvas::addMatchNormal); +} + +void FindCanvas::drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + // Pass in the first y coordinate for y so that we can check to see whether + // it is lower than the last draw call (to check if we are continuing to + // another line). + findHelper(text, byteLength, paint, (const SkScalar*) pos, pos[0].fY, + &FindCanvas::addMatchPos); +} + +void FindCanvas::drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + findHelper(text, byteLength, paint, xpos, constY, + &FindCanvas::addMatchPosH); +} + +/* The current behavior is to skip substring matches. This means that in the + * string + * batbatbat + * a search for + * batbat + * will return 1 match. If the desired behavior is to return 2 matches, define + * INCLUDE_SUBSTRING_MATCHES to be 1. + */ +#define INCLUDE_SUBSTRING_MATCHES 0 + +// Need a quick way to know a maximum distance between drawText calls to know if +// they are part of the same logical phrase when searching. By crude +// inspection, half the point size seems a good guess at the width of a space +// character. +static inline SkScalar approximateSpaceWidth(const SkPaint& paint) { + return SkScalarHalf(paint.getTextSize()); +} + +void FindCanvas::findHelper(const void* text, size_t byteLength, + const SkPaint& paint, const SkScalar positions[], + SkScalar y, + SkRect (FindCanvas::*addMatch)(int index, + const SkPaint& paint, int count, + const uint16_t* glyphs, + const SkScalar positions[], SkScalar y)) { + SkASSERT(paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); + SkASSERT(mMatches); + GlyphSet* glyphSet = getGlyphs(paint); + const int count = glyphSet->getCount(); + int numCharacters = byteLength >> 1; + const uint16_t* chars = (const uint16_t*) text; + // This block will check to see if we are continuing from another line. If + // so, the user needs to have added a space, which we do not draw. + if (mWorkingIndex) { + SkPoint newY; + getTotalMatrix().mapXY(0, y, &newY); + SkIRect workingBounds = mWorkingRegion.getBounds(); + int newYInt = SkScalarRound(newY.fY); + if (workingBounds.fTop > newYInt) { + // The new text is above the working region, so we know it's not + // a continuation. + resetWorkingCanvas(); + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + } else if (workingBounds.fBottom < newYInt) { + // Now we know that this line is lower than our partial match. + SkPaint clonePaint(paint); + clonePaint.setTextEncoding(SkPaint::kUTF8_TextEncoding); + uint16_t space; + clonePaint.textToGlyphs(" ", 1, &space); + if (glyphSet->characterMatches(space, mWorkingIndex)) { + mWorkingIndex++; + if (mWorkingIndex == count) { + // We already know that it is not clipped out because we + // checked for that before saving the working region. + insertMatchInfo(mWorkingRegion); + + resetWorkingCanvas(); + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + // We have found a match, so continue on this line from + // scratch. + } + } else { + resetWorkingCanvas(); + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + } + } + // If neither one is true, then we are likely continuing on the same + // line, but are in a new draw call because the paint has changed. In + // this case, we can continue without adding a space. + } + // j is the position in the search text + // Start off with mWorkingIndex in case we are continuing from a prior call + int j = mWorkingIndex; + // index is the position in the drawn text + int index = 0; + for ( ; index != numCharacters; index++) { + if (glyphSet->characterMatches(chars[index], j)) { + // The jth character in the search text matches the indexth position + // in the drawn text, so increase j. + j++; + if (j != count) { + continue; + } + // The last count characters match, so we found the entire + // search string. + int remaining = count - mWorkingIndex; + int matchIndex = index - remaining + 1; + // Set up a pointer to the matching text in 'chars'. + const uint16_t* glyphs = chars + matchIndex; + SkRect rect = (this->*addMatch)(matchIndex, paint, + remaining, glyphs, positions, y); + rect.inset(mOutset, mOutset); + // We need an SkIRect for SkRegion operations. + SkIRect iRect; + rect.roundOut(&iRect); + // If the rectangle is partially clipped, assume that the text is + // not visible, so skip this match. + if (getTotalClip().contains(iRect)) { + SkRegion regionToAdd(iRect); + if (!mWorkingRegion.isEmpty()) { + // If this is on the same line as our working region, make + // sure that they are close enough together that they are + // supposed to be part of the same text string. + // The width of two spaces has arbitrarily been chosen. + const SkIRect& workingBounds = mWorkingRegion.getBounds(); + if (workingBounds.fTop <= iRect.fBottom && + workingBounds.fBottom >= iRect.fTop && + SkIntToScalar(iRect.fLeft - workingBounds.fRight) > + approximateSpaceWidth(paint)) { + index = -1; // Will increase to 0 on next run + // In this case, we need to start from the beginning of + // the text being searched and our search term. + j = 0; + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + continue; + } + // Add the mWorkingRegion, which contains rectangles from + // the previous line(s). + regionToAdd.op(mWorkingRegion, SkRegion::kUnion_Op); + } + insertMatchInfo(regionToAdd); +#if INCLUDE_SUBSTRING_MATCHES + // Reset index to the location of the match and reset j to the + // beginning, so that on the next iteration of the loop, index + // will advance by 1 and we will compare the next character in + // chars to the first character in the GlyphSet. + index = matchIndex; +#endif + } else { + // This match was clipped out, so begin looking at the next + // character from our hidden match + index = matchIndex; + } + // Whether the clip contained it or not, we need to start over + // with our recording canvas + resetWorkingCanvas(); + } else { + // Index needs to be set to index - j + 1. + // This is a ridiculous case, but imagine the situation where the + // user is looking for the string "jjog" in the drawText call for + // "jjjog". The first two letters match. However, when the index + // is 2, and we discover that 'o' and 'j' do not match, we should go + // back to 1, where we do, in fact, have a match + // FIXME: This does not work if (as in our example) "jj" is in one + // draw call and "jog" is in the next. Doing so would require a + // stack, keeping track of multiple possible working indeces and + // regions. This is likely an uncommon case. + index = index - j; // index will be increased by one on the next + // iteration + } + // We reach here in one of two cases: + // 1) We just completed a match, so any working rectangle/index is no + // longer needed, and we will start over from the beginning + // 2) The glyphs do not match, so we start over at the beginning of + // the search string. + j = 0; + mWorkingIndex = 0; + mWorkingRegion.setEmpty(); + } + // At this point, we have searched all of the text in the current drawText + // call. + // Keep track of a partial match that may start on this line. + if (j > 0) { // if j is greater than 0, we have a partial match + int relativeCount = j - mWorkingIndex; // Number of characters in this + // part of the match. + int partialIndex = index - relativeCount; // Index that starts our + // partial match. + const uint16_t* partialGlyphs = chars + partialIndex; + SkRect partial = (this->*addMatch)(partialIndex, paint, relativeCount, + partialGlyphs, positions, y); + partial.inset(mOutset, mOutset); + SkIRect dest; + partial.roundOut(&dest); + // Only save a partial if it is in the current clip. + if (getTotalClip().contains(dest)) { + mWorkingRegion.op(dest, SkRegion::kUnion_Op); + mWorkingIndex = j; + return; + } + } + // This string doesn't go into the next drawText, so reset our working + // variables + mWorkingRegion.setEmpty(); + mWorkingIndex = 0; +} + +SkCanvas* FindCanvas::getWorkingCanvas() { + if (!mWorkingPicture) { + mWorkingPicture = new SkPicture; + mWorkingCanvas = mWorkingPicture->beginRecording(0,0); + } + return mWorkingCanvas; +} + +GlyphSet* FindCanvas::getGlyphs(const SkPaint& paint) { + SkTypeface* typeface = paint.getTypeface(); + GlyphSet* end = mGlyphSets.end(); + for (GlyphSet* ptr = mGlyphSets.begin();ptr != end; ptr++) { + if (ptr->getTypeface() == typeface) { + return ptr; + } + } + + GlyphSet set(paint, mLowerText, mUpperText, mLength); + *mGlyphSets.append() = set; + return &(mGlyphSets.top()); +} + +void FindCanvas::insertMatchInfo(const SkRegion& region) { + mNumFound++; + mWorkingPicture->endRecording(); + MatchInfo matchInfo; + mMatches->append(matchInfo); + mMatches->last().set(region, mWorkingPicture); +} + +void FindCanvas::resetWorkingCanvas() { + mWorkingPicture->unref(); + mWorkingPicture = 0; + // Do not need to reset mWorkingCanvas itself because we only access it via + // getWorkingCanvas. +} diff --git a/WebKit/android/nav/FindCanvas.h b/WebKit/android/nav/FindCanvas.h new file mode 100644 index 0000000..5d79b4c --- /dev/null +++ b/WebKit/android/nav/FindCanvas.h @@ -0,0 +1,207 @@ +/* + * Copyright 2008, 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef Find_Canvas_h +#define Find_Canvas_h + +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkRegion.h" +#include "SkTDArray.h" +#include "icu/unicode/umachine.h" +#include "wtf/Vector.h" + +class SkRect; +class SkTypeface; + +// Stores both region information and an SkPicture of the match, so that the +// region can be drawn, followed by drawing the matching text on top of it. +// This class owns its SkPicture +class MatchInfo { +public: + MatchInfo(); + ~MatchInfo(); + MatchInfo(const MatchInfo& src); + const SkRegion& getLocation() const { return m_location; } + // Return a pointer to our picture, representing the matching text. Does + // not transfer ownership of the picture. + SkPicture* getPicture() const { return m_picture; } + // This will make a copy of the region, and increase the ref count on the + // SkPicture. If this MatchInfo already had one, unref it. + void set(const SkRegion& region, SkPicture* pic); +private: + MatchInfo& operator=(MatchInfo& src); + SkRegion m_location; + SkPicture* m_picture; +}; + +// A class containing a typeface for reference, the length in glyphs, and +// the upper and lower case representations of the search string. +class GlyphSet { +public: + GlyphSet(const SkPaint& paint, const UChar* lower, const UChar* upper, + size_t byteLength); + ~GlyphSet(); + GlyphSet& operator=(GlyphSet& src); + + // Return true iff c matches one of our glyph arrays at index + bool characterMatches(uint16_t c, int index); + + int getCount() const { return mCount; } + + const SkTypeface* getTypeface() const { return mTypeface; } + +private: + // Disallow copy constructor + GlyphSet(GlyphSet& src) { } + + // mTypeface is used for comparison only + const SkTypeface* mTypeface; + // mLowerGlyphs points to all of our storage space: the lower set followed + // by the upper set. mUpperGlyphs is purely a convenience pointer to the + // start of the upper case glyphs. + uint16_t* mLowerGlyphs; + uint16_t* mUpperGlyphs; + // mCount is the number of glyphs of the search string. Must be the same + // for both the lower case set and the upper case set. + int mCount; + + // Arbitrarily chose the maximum storage to use in the GlyphSet. This is + // based on the length of the word being searched. If users are always + // searching for 3 letter words (for example), an ideal number would be 3. + // Each time the user searches for a word longer than (in this case, 3) that + // will result in calling new/delete. + enum Storage { + MAX_STORAGE_COUNT = 16 + }; + // In order to eliminate new/deletes, create storage that will be enough + // most of the time + uint16_t mStorage[2*MAX_STORAGE_COUNT]; +}; + +class FindBounder : public SkBounder { +public: + FindBounder() {} + ~FindBounder() {} +protected: + virtual bool onIRect(const SkIRect&) { return false; } +}; + +class FindCanvas : public SkCanvas { +public: + FindCanvas(int width, int height, const UChar* , const UChar*, + size_t byteLength); + + virtual ~FindCanvas(); + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint); + + /* FIXME: This path has not been tested. */ + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint); + + /* Also untested */ + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint); + + /* Not sure what to do here or for drawTextOnPathHV */ + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + } + + int found() const { return mNumFound; } + + // This method detaches our array of matches and passes ownership to + // the caller, who is then responsible for deleting them. + WTF::Vector<MatchInfo>* detachMatches() { + WTF::Vector<MatchInfo>* array = mMatches; + mMatches = NULL; + return array; + } + +private: + // These calls are made by findHelper to store information about each match + // that is found. They return a rectangle which is used to highlight the + // match. They also add to our SkPicture (which can be accessed with + // getDrawnMatches) a draw of each match. This way it can be drawn after + // the rectangle. The rect that is returned is in device coordinates. + SkRect addMatchNormal(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar pos[], SkScalar y); + + SkRect addMatchPos(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar xPos[], SkScalar /* y */); + + SkRect addMatchPosH(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar position[], SkScalar constY); + + // Helper for each of our draw calls + void findHelper(const void* text, size_t byteLength, const SkPaint& paint, + const SkScalar xPos[], SkScalar y, + SkRect (FindCanvas::*addMatch)(int index, + const SkPaint& paint, int count, const uint16_t* glyphs, + const SkScalar pos[], SkScalar y)); + + // If we already have a working canvas, grab it. Otherwise, create a new + // one. + SkCanvas* getWorkingCanvas(); + + // Return the set of glyphs and its count for the text being searched for + // and the parameter paint. If one has already been created and cached + // for this paint, use it. If not, create a new one and cache it. + GlyphSet* getGlyphs(const SkPaint& paint); + + // Store all the accumulated info about a match in our vector. + void insertMatchInfo(const SkRegion& region); + + // Throw away our cumulative information about our working SkCanvas. After + // this call, next call to getWorkingCanvas will create a new one. + void resetWorkingCanvas(); + + // Since we may transfer ownership of this array (see detachRects()), we + // hold a pointer to the array instead of just the array itself. + WTF::Vector<MatchInfo>* mMatches; + const UChar* mLowerText; + const UChar* mUpperText; + size_t mLength; + FindBounder mBounder; + int mNumFound; + SkScalar mOutset; + SkTDArray<GlyphSet> mGlyphSets; + + SkPicture* mWorkingPicture; + SkCanvas* mWorkingCanvas; + SkRegion mWorkingRegion; + int mWorkingIndex; +}; + +#endif // Find_Canvas_h + diff --git a/WebKit/android/nav/SelectText.cpp b/WebKit/android/nav/SelectText.cpp new file mode 100644 index 0000000..7bdbf14 --- /dev/null +++ b/WebKit/android/nav/SelectText.cpp @@ -0,0 +1,263 @@ +/* + * Copyright 2008, 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 APPLE COMPUTER, INC. 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. + */ +#define LOG_TAG "webcoreglue" + +#include "CachedPrefix.h" +#include "SelectText.h" +#include "SkBitmap.h" +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkMatrix.h" +#include "SkPicture.h" +#include "SkPoint.h" +#include "SkRect.h" +#include "SkRegion.h" + +class CommonCheck : public SkBounder { +public: + CommonCheck() : mMatrix(NULL), mPaint(NULL) {} + + virtual void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y) { + mMatrix = &matrix; + mPaint = &paint; + mY = y; + mBase = mBottom = mTop = INT_MAX; + } + + int base() { + if (mBase == INT_MAX) { + SkPoint result; + mMatrix->mapXY(0, mY, &result); + mBase = SkScalarFloor(result.fY); + } + return mBase; + } + + int bottom() { + if (mBottom == INT_MAX) { + SkPoint result; + SkPaint::FontMetrics metrics; + mPaint->getFontMetrics(&metrics); + mMatrix->mapXY(0, metrics.fDescent + mY, &result); + mBottom = SkScalarCeil(result.fY); + } + return mBottom; + } + + int top() { + if (mTop == INT_MAX) { + SkPoint result; + SkPaint::FontMetrics metrics; + mPaint->getFontMetrics(&metrics); + mMatrix->mapXY(0, metrics.fAscent + mY, &result); + mTop = SkScalarFloor(result.fY); + } + return mTop; + } + +protected: + const SkMatrix* mMatrix; + const SkPaint* mPaint; + int mBase; + int mBottom; + int mTop; + SkScalar mY; +}; + +class FirstCheck : public CommonCheck { +public: + FirstCheck(int x, int y) + : mDistance(INT_MAX), mFocusX(x), mFocusY(y) { + mBestBounds.setEmpty(); + } + + const SkIRect& bestBounds() { + DBG_NAV_LOGD("mBestBounds:(%d, %d, %d, %d) mTop=%d mBottom=%d", + mBestBounds.fLeft, mBestBounds.fTop, mBestBounds.fRight, + mBestBounds.fBottom, mTop, mBottom); + return mBestBounds; + } + + void offsetBounds(int dx, int dy) { + mBestBounds.offset(dx, dy); + } + + virtual bool onIRect(const SkIRect& rect) { + int dx = ((rect.fLeft + rect.fRight) >> 1) - mFocusX; + int dy = ((top() + bottom()) >> 1) - mFocusY; + int distance = dx * dx + dy * dy; +#ifdef EXTRA_NOISY_LOGGING + if (distance < 500 || abs(distance - mDistance) < 500) + DBG_NAV_LOGD("distance=%d mDistance=%d", distance, mDistance); +#endif + if (mDistance > distance) { + mDistance = distance; + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); +#ifdef EXTRA_NOISY_LOGGING + DBG_NAV_LOGD("mBestBounds={%d,%d,r=%d,b=%d}", + mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom); +#endif + } + return false; + } +protected: + SkIRect mBestBounds; + int mDistance; + int mFocusX; + int mFocusY; +}; + +class MultilineBuilder : public CommonCheck { +public: + MultilineBuilder(const SkIRect& start, const SkIRect& end, int dx, int dy, + SkRegion* region) + : mStart(start), mEnd(end), mSelectRegion(region), mCapture(false) { + mLast.setEmpty(); + mLastBase = INT_MAX; + mStart.offset(-dx, -dy); + mEnd.offset(-dx, -dy); + } + + virtual bool onIRect(const SkIRect& rect) { + bool captureLast = false; + if ((rect.fLeft == mStart.fLeft && rect.fRight == mStart.fRight && + top() == mStart.fTop && bottom() == mStart.fBottom) || + (rect.fLeft == mEnd.fLeft && rect.fRight == mEnd.fRight && + top() == mEnd.fTop && bottom() == mEnd.fBottom)) { + captureLast = mCapture; + mCapture ^= true; + } + if (mCapture || captureLast) { + SkIRect full; + full.set(rect.fLeft, top(), rect.fRight, bottom()); + if ((mLast.fTop < base() && mLast.fBottom >= base()) + || (mLastBase <= full.fBottom && mLastBase > full.fTop)) { + if (full.fLeft > mLast.fRight) + full.fLeft = mLast.fRight; + else if (full.fRight < mLast.fLeft) + full.fRight = mLast.fLeft; + } + mSelectRegion->op(full, SkRegion::kUnion_Op); + mLast = full; + mLastBase = base(); + if (mStart == mEnd) + mCapture = false; + } + return false; + } +protected: + SkIRect mStart; + SkIRect mEnd; + SkIRect mLast; + int mLastBase; + SkRegion* mSelectRegion; + bool mCapture; +}; + +class TextCanvas : public SkCanvas { +public: + + TextCanvas(CommonCheck* bounder, const SkPicture& picture, const SkIRect& area) + : mBounder(*bounder) { + setBounder(bounder); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, area.width(), + area.height()); + setBitmapDevice(bitmap); + translate(SkIntToScalar(-area.fLeft), SkIntToScalar(-area.fTop)); + } + + virtual ~TextCanvas() { + setBounder(NULL); + } + + virtual void drawPaint(const SkPaint& paint) { + } + + virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + } + + virtual void drawRect(const SkRect& rect, const SkPaint& paint) { + } + + virtual void drawPath(const SkPath& path, const SkPaint& paint) { + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, + const SkMatrix& matrix, const SkPaint& paint) { + } + + virtual void drawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint = NULL) { + } + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) { + mBounder.setUp(paint, getTotalMatrix(), y); + SkCanvas::drawText(text, byteLength, x, y, paint); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) { + mBounder.setUp(paint, getTotalMatrix(), constY); + SkCanvas::drawPosTextH(text, byteLength, xpos, constY, paint); + } + + virtual void drawVertices(VertexMode vmode, int vertexCount, + const SkPoint vertices[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) { + } + + CommonCheck& mBounder; +}; + +void CopyPaste::buildSelection(const SkPicture& picture, const SkIRect& area, + const SkIRect& selStart, const SkIRect& selEnd, SkRegion* region) { + DBG_NAV_LOGD("area=(%d, %d, %d, %d) selStart=(%d, %d, %d, %d)" + " selEnd=(%d, %d, %d, %d)", + area.fLeft, area.fTop, area.fRight, area.fBottom, + selStart.fLeft, selStart.fTop, selStart.fRight, selStart.fBottom, + selEnd.fLeft, selEnd.fTop, selEnd.fRight, selEnd.fBottom); + MultilineBuilder builder(selStart, selEnd, area.fLeft, area.fTop, region); + TextCanvas checker(&builder, picture, area); + checker.drawPicture(const_cast<SkPicture&>(picture)); + region->translate(area.fLeft, area.fTop); +} + +SkIRect CopyPaste::findClosest(const SkPicture& picture, const SkIRect& area, + int x, int y) { + FirstCheck _check(x - area.fLeft, y - area.fTop); + DBG_NAV_LOGD("area=(%d, %d, %d, %d) x=%d y=%d", area.fLeft, area.fTop, + area.fRight, area.fBottom, x, y); + TextCanvas checker(&_check, picture, area); + checker.drawPicture(const_cast<SkPicture&>(picture)); + _check.offsetBounds(area.fLeft, area.fTop); + return _check.bestBounds(); +} diff --git a/WebKit/android/nav/SelectText.h b/WebKit/android/nav/SelectText.h new file mode 100644 index 0000000..b991198 --- /dev/null +++ b/WebKit/android/nav/SelectText.h @@ -0,0 +1,42 @@ +/* + * Copyright 2008, 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 APPLE COMPUTER, INC. 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. + */ + +#ifndef SELECT_TEXT_H +#define SELECT_TEXT_H + +class SkPicture; +struct SkIRect; +struct SkIPoint; +class SkRegion; + +class CopyPaste { +public: + static void buildSelection(const SkPicture& , const SkIRect& area, + const SkIRect& selStart, const SkIRect& selEnd, SkRegion* region); + static SkIRect findClosest(const SkPicture& , const SkIRect& area, + int x, int y); +}; + +#endif diff --git a/WebKit/android/nav/WebView.cpp b/WebKit/android/nav/WebView.cpp new file mode 100644 index 0000000..52bb6a5 --- /dev/null +++ b/WebKit/android/nav/WebView.cpp @@ -0,0 +1,2322 @@ +/* + * 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 APPLE COMPUTER, INC. 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. + */ + +#define LOG_TAG "webviewglue" + +#include <config.h> + +#include "android_graphics.h" +#include "AndroidLog.h" +#include "AtomicString.h" +#include "CachedFrame.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "FindCanvas.h" +#include "Frame.h" +#include "GraphicsJNI.h" +#include "IntPoint.h" +#include "IntRect.h" +#include "Node.h" +#include "PlatformGraphicsContext.h" +#include "PlatformString.h" +#include "SelectText.h" +#include "SkBlurMaskFilter.h" +#include "SkCanvas.h" +#include "SkCornerPathEffect.h" +#include "SkDumpCanvas.h" +#include "SkPath.h" +#include "SkPicture.h" +#include "SkPixelXorXfermode.h" +#include "SkRect.h" +#include "SkTime.h" +#include "WebCoreJni.h" +#include "WebViewCore.h" +#include "jni_utility.h" + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +#ifdef GET_NATIVE_VIEW +#undef GET_NATIVE_VIEW +#endif + +#define GET_NATIVE_VIEW(env, obj) ((WebView*)env->GetIntField(obj, gWebViewField)) + +#include <ui/KeycodeLabels.h> +#include <JNIHelp.h> +#include <jni.h> + +#define REPLAY_BUFFER_SIZE 4096 + +namespace android { + +struct CommonParams { + enum Trigger { + NoData, + ClearFocusParams, + FirstMoveFocusParams, + MoveFocusParams, + MotionUpParams + } m_trigger; + int m_generation; +}; + +struct CacheParams { + void setFocus(const CachedNode* node, + const CachedFrame* frame, const CachedRoot* root, + const WebCore::IntPoint& focusLocation) + { + m_node = (WebCore::Node*) (node ? node->nodePointer() : 0); + m_frame = (WebCore::Frame*) (node ? frame->framePointer() : 0); + m_x = focusLocation.x(); + m_y = focusLocation.y(); + } + + WebCore::Node* m_node; + WebCore::Frame* m_frame; + int m_x; + int m_y; +}; + +struct ClearFocusParams { + CommonParams d; + CacheParams c; + int m_x; + int m_y; +}; + +struct MotionUpParams { + CommonParams d; + int m_x; + int m_y; + int m_slop; + bool m_isClick; +}; + +struct FirstMoveFocusParams { + CommonParams d; + int m_keyCode; + int m_count; + bool m_ignoreScroll; +}; + +struct MoveFocusParams { + FirstMoveFocusParams d; + CacheParams c; + void* m_sentFocus; + WebCore::IntRect m_sentBounds; + WebCore::IntRect m_visibleRect; + CachedHistory m_history; // FIXME: make this a subset + int m_xMax; + int m_yMax; +}; + +typedef MoveFocusParams LargestParams; + +#if DEBUG_NAV_UI +static const char* TriggerNames[] = { + "*** no data ! ***", + "clearFocus", + "firstMoveFocus", + "moveFocus", + "motionUp" +}; +#endif + +class FocusReplay { +public: +FocusReplay() : m_start(m_buffer), m_end(m_buffer), m_lastGeneration(0) +{ +} + +// find the most recent common data +void add(const CommonParams& data, size_t len) +{ + DBG_NAV_LOGD("m_start=%d m_end=%d trigger=%s moveGeneration=%d", m_start - m_buffer, + m_end - m_buffer, TriggerNames[data.m_trigger], data.m_generation); + m_lastGeneration = data.m_generation; + char* limit = m_buffer + sizeof(m_buffer); + int used = m_end - m_start; + if (used < 0) + used += sizeof(m_buffer); + int needed = (int) len - ((int) sizeof(m_buffer) - used); + if (needed >= 0) + reclaim(++needed); + if (m_end + len <= limit) { + memcpy(m_end, (void*) &data, len); + m_end += len; + DBG_NAV_LOGD("m_start=%d m_end=%d", m_start - m_buffer, m_end - m_buffer); + return; + } + size_t partial = limit - m_end; + memcpy(m_end, (void*) &data, partial); + const void* remainder = (const void*) ((const char*) &data + partial); + partial = len - partial; + memcpy(m_buffer, remainder, partial); + m_end = m_buffer + partial; + DBG_NAV_LOGD("wrap m_start=%d m_end=%d", + m_start - m_buffer, m_end - m_buffer); +} + +int count() +{ + DBG_NAV_LOGD("m_start=%d m_end=%d", + m_start - m_buffer, m_end - m_buffer); + if (m_start == m_end) + return 0; + char* limit = m_buffer + sizeof(m_buffer); + char* saveStart = m_start; + int result = 0; + while (true) { + ++result; + m_start += triggerSize(); + if (m_start == m_end) + break; + if (m_start < limit) + continue; + m_start -= sizeof(m_buffer); + if (m_start == m_end) + break; + } + m_start = saveStart; + DBG_NAV_LOGD("count=%d", result); + return result; +} + +void discard(int generation) +{ + DBG_NAV_LOGD("generation=%d", generation); + LargestParams storage; + const CommonParams& params = storage.d.d; + char* pos = position(); + retrieve(&storage.d.d); + if (params.m_generation > generation) { + DBG_NAV_LOGD("params.m_generation=%d > generation=%d", + params.m_generation, generation); + rewind(pos); + DBG_NAV_LOGD("m_start=%d m_end=%d", m_start - m_buffer, m_end - m_buffer); + return; + } + LOG_ASSERT(params.m_generation == generation, "params.m_generation != generation"); + DBG_NAV_LOGD("m_start=%d m_end=%d", m_start - m_buffer, m_end - m_buffer); +} + +int lastAdd() +{ + return m_lastGeneration; +} + +char* position() +{ + return m_start; +} + +int retrieve(CommonParams* data) +{ + if (m_end == m_start) { + // changed from LOGD to LOGV, as it always fires when I click to center + // text (mrr) + LOGV("%s *** no data to retrieve (error condition) ***", __FUNCTION__); + data->m_trigger = CommonParams::NoData; + return data->m_generation = INT_MAX; + } + DBG_NAV_LOGD("m_start=%d m_end=%d", + m_start - m_buffer, m_end - m_buffer); + char* limit = m_buffer + sizeof(m_buffer); + size_t size = triggerSize(); + if (m_start < m_end) { + LOG_ASSERT((size_t) (m_end - m_start) >= size, "m_end - m_start < size"); + memcpy(data, m_start, size); + m_start += size; + } else { + int partial = limit - m_start; + if (partial > (int) size) + partial = size; + memcpy(data, m_start, partial); + m_start += partial; + void* remainder = (void*) ((char*) data + partial); + partial = size - partial; + if (partial > 0) { + memcpy(remainder, m_buffer, partial); + m_start = m_buffer + partial; + LOG_ASSERT(m_start <= m_end, "m_start > m_end"); + } + } + if (m_start == limit) { + m_start = m_buffer; + if (m_end == limit) + m_end = m_buffer; + } + DBG_NAV_LOGD("m_start=%d m_end=%d trigger=%s moveGeneration=%d", + m_start - m_buffer, m_end - m_buffer, TriggerNames[data->m_trigger], + data->m_generation); + return data->m_generation; +} + +void rewind(char* pos) +{ + m_start = pos; +} + +private: +void reclaim(int needed) +{ + DBG_NAV_LOGD("needed=%d", needed); + char* limit = m_buffer + sizeof(m_buffer); + do { + size_t size = triggerSize(); + m_start += size; + needed -= size; + if (m_start >= limit) { + m_start = m_buffer + (m_start - limit); + if (m_end == limit) + m_end = m_buffer; + } + } while (needed > 0 && m_start != m_end); + DBG_NAV_LOGD("m_start=%d m_end=%d", + m_start - m_buffer, m_end - m_buffer); +} + +size_t triggerSize() +{ + LOG_ASSERT(m_start != m_end, "m_start == m_end"); + char* limit = m_buffer + sizeof(m_buffer); + LOG_ASSERT(m_start + sizeof(CommonParams::Trigger) <= limit, "trigger not in limit"); + CommonParams::Trigger trigger; + memcpy(&trigger, m_start, sizeof(trigger)); + switch (trigger) { + case CommonParams::ClearFocusParams: + return sizeof(ClearFocusParams); + case CommonParams::FirstMoveFocusParams: + return sizeof(FirstMoveFocusParams); + case CommonParams::MoveFocusParams: + return sizeof(MoveFocusParams); + case CommonParams::MotionUpParams: + return sizeof(MotionUpParams); + default: + LOG_ASSERT(0, "trigger undefined"); + } + return 0; +} + +char m_buffer[REPLAY_BUFFER_SIZE]; +char* m_start; +char* m_end; +int m_lastGeneration; +}; // end of helper class ReplayFocus + +static jfieldID gWebViewField; + +//------------------------------------- + +static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[]) +{ + jmethodID m = env->GetMethodID(clazz, name, signature); + LOG_ASSERT(m, "Could not find method %s", name); + return m; +} + +//------------------------------------- +// This class provides JNI for making calls into native code from the UI side +// of the multi-threaded WebView. +class WebView +{ +public: +enum FrameCachePermission { + DontAllowNewer, + AllowNewer, + AllowNewest +}; + +enum OutOfFocusFix { + DoNothing, + ClearTextEntry, + UpdateTextEntry +}; + +struct JavaGlue { + jobject m_obj; + jmethodID m_clearTextEntry; + jmethodID m_overrideLoading; + jmethodID m_scrollBy; + jmethodID m_sendFinalFocus; + jmethodID m_sendKitFocus; + jmethodID m_sendMotionUp; + jmethodID m_setFocusData; + jmethodID m_getScaledMaxXScroll; + jmethodID m_getScaledMaxYScroll; + jmethodID m_getVisibleRect; + jmethodID m_updateTextEntry; + jmethodID m_displaySoftKeyboard; + jmethodID m_viewInvalidate; + jmethodID m_viewInvalidateRect; + jmethodID m_postInvalidateDelayed; + jfieldID m_rectLeft; + jfieldID m_rectTop; + jmethodID m_rectWidth; + jmethodID m_rectHeight; + jfieldID m_focusNode; + jmethodID m_setAll; + AutoJObject object(JNIEnv* env) { + return getRealObject(env, m_obj); + } +} m_javaGlue; + +WebView(JNIEnv* env, jobject javaWebView, int viewImpl) +{ + jclass clazz = env->FindClass("android/webkit/WebView"); + // m_javaGlue = new JavaGlue; + m_javaGlue.m_obj = adoptGlobalRef(env, javaWebView); + m_javaGlue.m_scrollBy = GetJMethod(env, clazz, "setContentScrollBy", "(IIZ)V"); + m_javaGlue.m_clearTextEntry = GetJMethod(env, clazz, "clearTextEntry", "()V"); + m_javaGlue.m_overrideLoading = GetJMethod(env, clazz, "overrideLoading", "(Ljava/lang/String;)V"); + m_javaGlue.m_sendFinalFocus = GetJMethod(env, clazz, "sendFinalFocus", "(IIII)V"); + m_javaGlue.m_sendKitFocus = GetJMethod(env, clazz, "sendKitFocus", "()V"); + m_javaGlue.m_sendMotionUp = GetJMethod(env, clazz, "sendMotionUp", "(IIIIIIIZZ)V"); + m_javaGlue.m_setFocusData = GetJMethod(env, clazz, "setFocusData", "(IIIIIIZ)V"); + m_javaGlue.m_getScaledMaxXScroll = GetJMethod(env, clazz, "getScaledMaxXScroll", "()I"); + m_javaGlue.m_getScaledMaxYScroll = GetJMethod(env, clazz, "getScaledMaxYScroll", "()I"); + m_javaGlue.m_getVisibleRect = GetJMethod(env, clazz, "sendOurVisibleRect", "()Landroid/graphics/Rect;"); + m_javaGlue.m_updateTextEntry = GetJMethod(env, clazz, "updateTextEntry", "()V"); + m_javaGlue.m_displaySoftKeyboard = GetJMethod(env, clazz, "displaySoftKeyboard", "()V"); + m_javaGlue.m_viewInvalidate = GetJMethod(env, clazz, "viewInvalidate", "()V"); + m_javaGlue.m_viewInvalidateRect = GetJMethod(env, clazz, "viewInvalidate", "(IIII)V"); + m_javaGlue.m_postInvalidateDelayed = GetJMethod(env, clazz, + "viewInvalidateDelayed", "(JIIII)V"); + jclass rectClass = env->FindClass("android/graphics/Rect"); + LOG_ASSERT(rectClass, "Could not find Rect class"); + m_javaGlue.m_rectLeft = env->GetFieldID(rectClass, "left", "I"); + m_javaGlue.m_rectTop = env->GetFieldID(rectClass, "top", "I"); + m_javaGlue.m_rectWidth = GetJMethod(env, rectClass, "width", "()I"); + m_javaGlue.m_rectHeight = GetJMethod(env, rectClass, "height", "()I"); + + // Set up class for updateFocusNode + jclass focusnodeClass = env->FindClass("android/webkit/WebView$FocusNode"); + LOG_ASSERT(focusnodeClass, "Could not find FocusNode class!"); + m_javaGlue.m_focusNode = env->GetFieldID(clazz, "mFocusNode", "Landroid/webkit/WebView$FocusNode;"); + m_javaGlue.m_setAll = GetJMethod(env, focusnodeClass, "setAll", "(ZZZZZIIIIIIIILjava/lang/String;Ljava/lang/String;I)V"); + env->DeleteLocalRef(focusnodeClass); + + env->SetIntField(javaWebView, gWebViewField, (jint)this); + m_viewImpl = (WebViewCore*) viewImpl; + m_frameCacheUI = 0; + m_navPictureUI = 0; + m_invalidNode = 0; + m_generation = 0; + m_heightCanMeasure = false; + m_followedLink = false; + m_lastDx = 0; + m_lastDxTime = 0; + m_ringAnimationEnd = 0; + m_selStart.setEmpty(); + m_selEnd.setEmpty(); + m_matches = 0; + m_hasCurrentLocation = false; + m_isFindPaintSetUp = false; +} + +~WebView() +{ + if (m_javaGlue.m_obj) + { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteGlobalRef(m_javaGlue.m_obj); + m_javaGlue.m_obj = 0; + } + delete m_frameCacheUI; + delete m_navPictureUI; + if (m_matches) + delete m_matches; +} + +void clearFocus(int x, int y, bool inval) +{ + DBG_NAV_LOGD("x=%d y=%d inval=%s", x, y, + inval ? "true" : "false"); + clearTextEntry(); + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + const CachedFrame* oldFrame = 0; + const CachedNode* oldFocusNode = root->currentFocus(&oldFrame); + WebCore::IntPoint focusLocation = WebCore::IntPoint(0, 0); + setFocusData(root->generation(), 0, 0, x, y, !oldFocusNode); + sendKitFocus(); + if (oldFocusNode) { + DBG_NAV_LOG("oldFocusNode"); + focusLocation = root->focusLocation(); + root->setCachedFocus(0, 0); + if (inval) + viewInvalidate(); + } + ClearFocusParams params; + params.d.m_trigger = CommonParams::ClearFocusParams; + params.d.m_generation = m_generation; + params.c.setFocus(oldFocusNode, oldFrame, root, focusLocation); + params.m_x = x; + params.m_y = y; + m_replay.add(params.d, sizeof(params)); +} + +void clearTextEntry() +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_clearTextEntry); + checkException(env); +} + +#if DUMP_NAV_CACHE +void debugDump() +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) + root->mDebug.print(); +} +#endif + +// Traverse our stored array of buttons that are in our picture, and update +// their subpictures according to their current focus state. +// Called from the UI thread. This is the one place in the UI thread where we +// access the buttons stored in the WebCore thread. +// hasFocus keeps track of whether the WebView has focus && windowFocus. +// If not, we do not want to draw the button in a focused or pressed state +void nativeRecordButtons(bool hasFocus, bool pressed, bool invalidate) +{ + bool focusIsButton = false; + const CachedNode* cachedFocus = 0; + // Lock the mutex, since we now share with the WebCore thread. + m_viewImpl->gButtonMutex.lock(); + if (m_viewImpl->m_buttons.size()) { + // Find the focused node so we can determine which node has focus, and + // therefore which state to paint them in. + // FIXME: In a future change, we should keep track of whether the focus + // has changed to short circuit (note that we would still need to update + // if we received new buttons from the WebCore thread). + WebCore::Node* focus = 0; + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + cachedFocus = root->currentFocus(); + if (cachedFocus) + focus = (WebCore::Node*) cachedFocus->nodePointer(); + } + + // Traverse the array, and update each button, depending on whether it + // is focused. + Container* end = m_viewImpl->m_buttons.end(); + for (Container* ptr = m_viewImpl->m_buttons.begin(); ptr != end; ptr++) { + WebCore::RenderSkinAndroid::State state; + if (ptr->matches(focus)) { + focusIsButton = true; + // If the WebView is out of focus/window focus, set the state to + // normal, but still keep track of the fact that the focus is a + // button + if (!hasFocus) { + state = WebCore::RenderSkinAndroid::kNormal; + } else if (m_followedLink || pressed) { + state = WebCore::RenderSkinAndroid::kPressed; + } else { + state = WebCore::RenderSkinAndroid::kFocused; + } + } else { + state = WebCore::RenderSkinAndroid::kNormal; + } + ptr->updateFocusState(state); + } + } + m_viewImpl->gButtonMutex.unlock(); + if (invalidate && cachedFocus && focusIsButton) { + const WebCore::IntRect& b = cachedFocus->getBounds(); + viewInvalidateRect(b.x(), b.y(), b.right(), b.bottom()); + } +} + +// These two functions separate out the particular look of the drawn find +// matches from the code that draws them. This function sets up the paints that +// are used to draw the matches. +void setUpFindPaint() +{ + // Set up the foreground paint + m_findPaint.setAntiAlias(true); + const SkScalar roundiness = SkIntToScalar(2); + SkCornerPathEffect* cornerEffect = new SkCornerPathEffect(roundiness); + m_findPaint.setPathEffect(cornerEffect); + m_findPaint.setARGB(255, 132, 190, 0); + + // Set up the background blur paint. + m_findBlurPaint.setAntiAlias(true); + m_findBlurPaint.setARGB(204, 0, 0, 0); + m_findBlurPaint.setPathEffect(cornerEffect); + cornerEffect->unref(); + SkMaskFilter* blurFilter = SkBlurMaskFilter::Create(SK_Scalar1, + SkBlurMaskFilter::kNormal_BlurStyle); + m_findBlurPaint.setMaskFilter(blurFilter)->unref(); + m_isFindPaintSetUp = true; +} + +// Draw the match specified by region to the canvas. +void drawMatch(const SkRegion& region, SkCanvas* canvas, bool focused) +{ + // For the match which has focus, use a filled paint. For the others, use + // a stroked paint. + if (focused) { + m_findPaint.setStyle(SkPaint::kFill_Style); + m_findBlurPaint.setStyle(SkPaint::kFill_Style); + } else { + m_findPaint.setStyle(SkPaint::kStroke_Style); + m_findPaint.setStrokeWidth(SK_Scalar1); + m_findBlurPaint.setStyle(SkPaint::kStroke_Style); + m_findBlurPaint.setStrokeWidth(SkIntToScalar(2)); + } + // Find the path for the current match + SkPath matchPath; + region.getBoundaryPath(&matchPath); + // Offset the path for a blurred shadow + SkPath blurPath; + matchPath.offset(SK_Scalar1, SkIntToScalar(2), &blurPath); + int saveCount = 0; + if (!focused) { + saveCount = canvas->save(); + canvas->clipPath(matchPath, SkRegion::kDifference_Op); + } + // Draw the blurred background + canvas->drawPath(blurPath, m_findBlurPaint); + if (!focused) { + canvas->restoreToCount(saveCount); + } + // Draw the foreground + canvas->drawPath(matchPath, m_findPaint); +} + +// Put a cap on the number of matches to draw. If the current page has more +// matches than this, only draw the focused match. +#define MAX_NUMBER_OF_MATCHES_TO_DRAW 101 + +void drawMatches(SkCanvas* canvas) +{ + if (!m_matches || !m_matches->size()) { + return; + } + if (m_findIndex >= m_matches->size()) { + m_findIndex = 0; + } + const MatchInfo& matchInfo = (*m_matches)[m_findIndex]; + const SkRegion& currentMatchRegion = matchInfo.getLocation(); + const SkIRect& currentMatchBounds = currentMatchRegion.getBounds(); + int left = currentMatchBounds.fLeft; + int top = currentMatchBounds.fTop; + int right = currentMatchBounds.fRight; + int bottom = currentMatchBounds.fBottom; + WebCore::IntRect visible; + getVisibleRect(&visible); + // Check to make sure that the highlighted match is on screen. If not, + // scroll it onscreen and return. + int dx = 0; + if (left < visible.x()) { + dx = left - visible.x(); + // Only scroll right if the entire width can fit on screen. + } else if (right > visible.right() && right - left < visible.width()) { + dx = right - visible.right(); + } + int dy = 0; + if (top < visible.y()) { + dy = top - visible.y(); + // Only scroll down if the entire height can fit on screen + } else if (bottom > visible.bottom() && bottom - top < visible.height()) { + dy = bottom - visible.bottom(); + } + if ((dx|dy)) { + scrollBy(dx, dy); + viewInvalidate(); + return; + } + // Set up the paints used for drawing the matches + if (!m_isFindPaintSetUp) + setUpFindPaint(); + + // Draw the current match + drawMatch(currentMatchRegion, canvas, true); + // Now draw the picture, so that it shows up on top of the rectangle + canvas->drawPicture(*matchInfo.getPicture()); + + // Draw the rest + unsigned numberOfMatches = m_matches->size(); + if (numberOfMatches > 1 + && numberOfMatches < MAX_NUMBER_OF_MATCHES_TO_DRAW) { + SkIRect visibleIRect; + android_setrect(&visibleIRect, visible); + for(unsigned i = 0; i < numberOfMatches; i++) { + // The current match has already been drawn + if (i == m_findIndex) + continue; + const SkRegion& region = (*m_matches)[i].getLocation(); + // Do not draw matches which intersect the current one, or if it is + // offscreen + if (currentMatchRegion.intersects(region) + || !region.intersects(visibleIRect)) + continue; + drawMatch(region, canvas, false); + } + } +} + +void drawFocusRing(SkCanvas* canvas) +{ + const CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + m_followedLink = false; + return; + } + const CachedNode* node = root->currentFocus(); + if (!node) { + DBG_NAV_LOG("!node"); + m_followedLink = false; + return; + } + if (!node->hasFocusRing()) { + DBG_NAV_LOG("!node->hasFocusRing()"); + return; + } + const WTF::Vector<WebCore::IntRect>& rings = node->focusRings(); + if (!rings.size()) { + DBG_NAV_LOG("!rings.size()"); + return; + } + + bool isButton = false; + m_viewImpl->gButtonMutex.lock(); + // If this is a button drawn by us (rather than webkit) do not draw the + // focus ring, since its focus will be shown by a change in what we draw. + // Should be in sync with recordButtons, since that will be called + // before this. + if (m_viewImpl->m_buttons.size() > 0) { + WebCore::Node* focusPointer = (WebCore::Node*) node->nodePointer(); + Container* end = m_viewImpl->m_buttons.end(); + for (Container* ptr = m_viewImpl->m_buttons.begin(); ptr != end; ptr++) { + if (ptr->matches(focusPointer)) { + isButton = true; + break; + } + } + } + m_viewImpl->gButtonMutex.unlock(); + WebCore::IntRect bounds = node->bounds(); + bounds.inflate(SkScalarCeil(FOCUS_RING_OUTER_DIAMETER)); + SkRect sbounds; + android_setrect(&sbounds, bounds); + if (canvas->quickReject(sbounds, SkCanvas::kAA_EdgeType)) { + DBG_NAV_LOG("canvas->quickReject"); + m_followedLink = false; + return; + } + FocusRing::Flavor flavor = FocusRing::NORMAL_FLAVOR; + if (!isButton) { + flavor = node->type() != NORMAL_CACHEDNODETYPE ? + FocusRing::FAKE_FLAVOR : node->nodePointer() == m_invalidNode ? + FocusRing::INVALID_FLAVOR : FocusRing::NORMAL_FLAVOR; + if (flavor != FocusRing::INVALID_FLAVOR && m_followedLink) { + flavor = (FocusRing::Flavor) (flavor + FocusRing::NORMAL_ANIMATING); + } +#if DEBUG_NAV_UI + const WebCore::IntRect& ring = rings[0]; + DBG_NAV_LOGD("cachedFocusNode=%d (nodePointer=%p) flavor=%s rings=%d" + " (%d, %d, %d, %d)", node->index(), node->nodePointer(), + flavor == FocusRing::FAKE_FLAVOR ? "FAKE_FLAVOR" : + flavor == FocusRing::INVALID_FLAVOR ? "INVALID_FLAVOR" : + flavor == FocusRing::NORMAL_ANIMATING ? "NORMAL_ANIMATING" : + flavor == FocusRing::FAKE_ANIMATING ? "FAKE_ANIMATING" : "NORMAL_FLAVOR", + rings.size(), ring.x(), ring.y(), ring.width(), ring.height()); +#endif + } + if (isButton || flavor >= FocusRing::NORMAL_ANIMATING) { + SkMSec time = SkTime::GetMSecs(); + if (time < m_ringAnimationEnd) { + // views assume that inval bounds coordinates are non-negative + bounds.intersect(WebCore::IntRect(0, 0, INT_MAX, INT_MAX)); + postInvalidateDelayed(m_ringAnimationEnd - time, bounds); + } else { + m_followedLink = false; + flavor = (FocusRing::Flavor) (flavor - FocusRing::NORMAL_ANIMATING); + } + } + if (!isButton) + FocusRing::DrawRing(canvas, rings, flavor); +} + +OutOfFocusFix fixOutOfDateFocus(bool useReplay) +{ + if (!m_frameCacheUI) { + DBG_NAV_LOG("!m_frameCacheUI"); + return DoNothing; + } + const CachedFrame* cachedFrame = 0; + const CachedNode* cachedFocusNode = m_frameCacheUI->currentFocus(&cachedFrame); + if (!cachedFocusNode) { + DBG_NAV_LOG("!cachedFocusNode"); + return DoNothing; + } + CachedRoot* webRoot = m_viewImpl->m_frameCacheKit; + if (!webRoot) { + DBG_NAV_LOG("!webRoot"); + return DoNothing; + } + int uiWidth = m_frameCacheUI->width(); + int webWidth = webRoot->width(); + if (uiWidth != webWidth) { + DBG_NAV_LOGD("uiWidth=%d webWidth=%d", uiWidth, webWidth); + return DoNothing; // allow text inputs to preserve their state + } else { + const WebCore::IntRect& cachedBounds = m_frameCacheUI->focusBounds(); + const CachedFrame* webFrame = 0; + const CachedNode* webFocusNode = webRoot->currentFocus(&webFrame); + DBG_NAV_LOGD("cachedBounds=(%d,%d,w=%d,h=%d) cachedFrame=%p (%d)" + " webFocusNode=%p (%d) webFrame=%p (%d)", + cachedBounds.x(), cachedBounds.y(), + cachedBounds.width(), cachedBounds.height(), + cachedFrame, cachedFrame ? cachedFrame->indexInParent() : -1, + webFocusNode, webFocusNode ? webFocusNode->index() : -1, + webFrame, webFrame ? webFrame->indexInParent() : -1); + if (webFocusNode && webFrame && webFrame->sameFrame(cachedFrame)) { + if (useReplay && !m_replay.count()) { + DBG_NAV_LOG("!m_replay.count()"); + return DoNothing; + } + if (webFocusNode->index() == cachedFocusNode->index()) { + DBG_NAV_LOG("index =="); + return DoNothing; + } + const WebCore::IntRect& webBounds = webRoot->focusBounds(); + DBG_NAV_LOGD("webBounds=(%d,%d,w=%d,h=%d)", + webBounds.x(), webBounds.y(), + webBounds.width(), webBounds.height()); + if (cachedBounds.contains(webBounds)) { + DBG_NAV_LOG("contains"); + return DoNothing; + } + if (webBounds.contains(cachedBounds)) { + DBG_NAV_LOG("webBounds contains"); + return DoNothing; + } + } + const CachedFrame* foundFrame = 0; + int x, y; + const CachedNode* found = findAt(webRoot, cachedBounds, &foundFrame, &x, &y); +#if DEBUG_NAV_UI + DBG_NAV_LOGD("found=%p (%d) frame=%p (%d)", + found, found ? found->index() : -1, + foundFrame, foundFrame ? foundFrame->indexInParent() : -1); + if (found) { + WebCore::IntRect newBounds = found->bounds(); + DBG_NAV_LOGD("found=(%d,%d,w=%d,h=%d) x=%d y=%d", + newBounds.x(), newBounds.y(), newBounds.width(), + newBounds.height(), x, y); + } +#endif + webRoot->setCachedFocus(const_cast<CachedFrame*>(foundFrame), + const_cast<CachedNode*>(found)); + if (found) + webRoot->rootHistory()->setNavBounds(found->bounds()); + WebCore::Frame* framePointer = foundFrame ? (WebCore::Frame*) foundFrame->framePointer() : 0; + WebCore::Node* nodePointer = found ? (WebCore::Node*) found->nodePointer() : 0; + setFocusData(webRoot->generation(), framePointer, nodePointer, x, y, !found); + sendFinalFocus(framePointer, nodePointer, x, y); + if (found && (found->isTextArea() || found->isTextField())) + return UpdateTextEntry; + } +checkOldFocus: + return cachedFocusNode->isTextArea() || cachedFocusNode->isTextField() ? ClearTextEntry : DoNothing; +} + +bool focusIsTextArea(FrameCachePermission allowNewer) +{ + CachedRoot* root = getFrameCache(allowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedNode* focus = root->currentFocus(); + if (!focus) + return false; + return focus->isTextArea() || focus->isTextField(); +} + +void focusRingBounds(WebCore::IntRect* bounds) +{ + DBG_NAV_LOGD("%s", ""); + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + const CachedNode* cachedNode = root->currentFocus(); + if (cachedNode) { + cachedNode->focusRingBounds(bounds); + DBG_NAV_LOGD("bounds={%d,%d,%d,%d}", bounds->x(), bounds->y(), + bounds->width(), bounds->height()); + return; + } + } + *bounds = WebCore::IntRect(0, 0, 0, 0); +} + +CachedRoot* getFrameCache(FrameCachePermission allowNewer) +{ + if (!m_viewImpl->m_updatedFrameCache) + return m_frameCacheUI; + m_viewImpl->gRecomputeFocusMutex.lock(); + bool recomputeInProgress = m_viewImpl->m_recomputeEvents.size() > 0; + m_viewImpl->gRecomputeFocusMutex.unlock(); + if (allowNewer != AllowNewest && recomputeInProgress) + return m_frameCacheUI; + if (allowNewer == DontAllowNewer && m_viewImpl->m_lastGeneration < m_generation) + return m_frameCacheUI; + DBG_NAV_LOGD("%s", "m_viewImpl->m_updatedFrameCache == true"); + m_viewImpl->gFrameCacheMutex.lock(); + OutOfFocusFix fix = DoNothing; + if (allowNewer != DontAllowNewer) + fix = fixOutOfDateFocus(m_viewImpl->m_useReplay); + delete m_frameCacheUI; + delete m_navPictureUI; + m_viewImpl->m_updatedFrameCache = false; + m_frameCacheUI = m_viewImpl->m_frameCacheKit; + m_navPictureUI = m_viewImpl->m_navPictureKit; + m_viewImpl->m_frameCacheKit = 0; + m_viewImpl->m_navPictureKit = 0; + m_viewImpl->gFrameCacheMutex.unlock(); + if (fix == UpdateTextEntry) + updateTextEntry(); + else if (fix == ClearTextEntry) + clearTextEntry(); + return m_frameCacheUI; +} + +int getScaledMaxXScroll() +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + int result = env->CallIntMethod(m_javaGlue.object(env).get(), m_javaGlue.m_getScaledMaxXScroll); + checkException(env); + return result; +} + +int getScaledMaxYScroll() +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + int result = env->CallIntMethod(m_javaGlue.object(env).get(), m_javaGlue.m_getScaledMaxYScroll); + checkException(env); + return result; +} + +void getVisibleRect(WebCore::IntRect* rect) +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject jRect = env->CallObjectMethod(m_javaGlue.object(env).get(), m_javaGlue.m_getVisibleRect); + checkException(env); + int left = (int) env->GetIntField(jRect, m_javaGlue.m_rectLeft); + checkException(env); + rect->setX(left); + int top = (int) env->GetIntField(jRect, m_javaGlue.m_rectTop); + checkException(env); + rect->setY(top); + int width = (int) env->CallIntMethod(jRect, m_javaGlue.m_rectWidth); + checkException(env); + rect->setWidth(width); + int height = (int) env->CallIntMethod(jRect, m_javaGlue.m_rectHeight); + checkException(env); + rect->setHeight(height); + env->DeleteLocalRef(jRect); + checkException(env); +} + +static CachedFrame::Direction KeyToDirection(KeyCode keyCode) +{ + switch (keyCode) { + case kKeyCodeDpadRight: + DBG_NAV_LOGD("keyCode=%s", "right"); + return CachedFrame::RIGHT; + case kKeyCodeDpadLeft: + DBG_NAV_LOGD("keyCode=%s", "left"); + return CachedFrame::LEFT; + case kKeyCodeDpadDown: + DBG_NAV_LOGD("keyCode=%s", "down"); + return CachedFrame::DOWN; + case kKeyCodeDpadUp: + DBG_NAV_LOGD("keyCode=%s", "up"); + return CachedFrame::UP; + default: + LOGD("------- bad key sent to WebView::moveFocus"); + return CachedFrame::UNINITIALIZED; + } +} + +bool invalidFrame(WebCore::Frame* frame, const CachedRoot* root) +{ + if (!frame) + return false; + int frameBuild = m_viewImpl->retrieveFrameGeneration(frame); + int rootBuild = root->generation(); + return frameBuild > rootBuild; +} + +WebCore::String imageURI(int x, int y) +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + return root ? root->imageURI(x, y) : WebCore::String(); +} + +bool focusNodeWantsKeyEvents() +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + const CachedNode* focus = root->currentFocus(); + if (focus) { + return focus->isWantsKeyEvents(); + } + } + return false; +} + +/* returns true if the key had no effect (neither scrolled nor changed focus) */ +bool moveFocus(int keyCode, int count, bool ignoreScroll, bool inval, + void* lastSentFocus, const WebCore::IntRect* lastSentBounds) +{ + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + setFocusData(0, 0, 0, 0, 0, true); + sendKitFocus(); // will build cache and retry + FirstMoveFocusParams params; + params.d.m_trigger = CommonParams::FirstMoveFocusParams; + params.d.m_generation = m_generation; + params.m_keyCode = keyCode; + params.m_count = count; + params.m_ignoreScroll = ignoreScroll; + m_replay.add(params.d, sizeof(params)); + return true; + } + + CachedFrame::Direction direction = KeyToDirection((KeyCode) keyCode); + const CachedFrame* cachedFrame, * oldFrame = 0; + const CachedNode* focus = root->currentFocus(&oldFrame); + WebCore::IntPoint focusLocation = root->focusLocation(); + DBG_NAV_LOGD("old focus %d (nativeNode=%p) focusLocation={%d, %d}", + focus ? focus->index() : 0, + focus ? focus->nodePointer() : 0, focusLocation.x(), focusLocation.y()); + WebCore::IntRect visibleRect; + getVisibleRect(&visibleRect); + DBG_NAV_LOGD("getVisibleRect %d,%d,%d,%d", + visibleRect.x(), visibleRect.y(), visibleRect.width(), visibleRect.height()); + root->setVisibleRect(visibleRect); + int xMax = getScaledMaxXScroll(); + int yMax = getScaledMaxYScroll(); + root->setMaxScroll(xMax, yMax); + CachedHistory savedHistory = *root->rootHistory(); + bool oldNodeIsTextArea = focusIsTextArea(DontAllowNewer); + const CachedNode* cachedNode = 0; + int dx = 0; + int dy = 0; + int counter = count; + if (!focus || !focus->isInput() || !m_followedLink) + root->setScrollOnly(m_followedLink); + while (--counter >= 0) { + WebCore::IntPoint scroll = WebCore::IntPoint(0, 0); + cachedNode = root->moveFocus(direction, &cachedFrame, &scroll); + dx += scroll.x(); + dy += scroll.y(); + } + DBG_NAV_LOGD("new focus %d (nativeNode=%p) focusLocation={%d, %d}", + cachedNode ? cachedNode->index() : 0, + cachedNode ? cachedNode->nodePointer() : 0, root->focusLocation().x(), + root->focusLocation().y()); + // If !m_heightCanMeasure (such as in the browser), we want to scroll no + // matter what + if (!ignoreScroll && (!m_heightCanMeasure || + !cachedNode || + (focus && focus->nodePointer() == cachedNode->nodePointer()))) + { + if (count == 1 && dx != 0 && dy == 0 && -m_lastDx == dx && + SkTime::GetMSecs() - m_lastDxTime < 1000) + root->checkForJiggle(&dx); + DBG_NAV_LOGD("scrollBy %d,%d", dx, dy); + if ((dx | dy)) + this->scrollBy(dx, dy); + m_lastDx = dx; + m_lastDxTime = SkTime::GetMSecs(); + ignoreScroll = true; // if move re-executes, don't scroll the second time + } + bool result = false; + if (cachedNode) { + WebCore::IntPoint pos; + root->setCachedFocus((CachedFrame*) cachedFrame, (CachedNode*) cachedNode); + root->getSimulatedMousePosition(&pos); + if (lastSentFocus == cachedNode->nodePointer() && lastSentBounds && + *lastSentBounds == cachedNode->bounds()) + { + sendFinalFocus((WebCore::Frame*) cachedFrame->framePointer(), + (WebCore::Node*) cachedNode->nodePointer(), pos.x(), pos.y()); + } else { + setFocusData(root->generation(), + (WebCore::Frame*) cachedFrame->framePointer(), + (WebCore::Node*) cachedNode->nodePointer(), pos.x(), pos.y(), + true); + sendKitFocus(); + if (inval) + viewInvalidate(); + MoveFocusParams params; + params.d.d.m_trigger = CommonParams::MoveFocusParams; + params.d.d.m_generation = m_generation; + params.c.setFocus(focus, oldFrame, root, focusLocation); + params.m_sentFocus = cachedNode->nodePointer(); + params.m_sentBounds = cachedNode->bounds(); + params.m_visibleRect = visibleRect; + params.m_history = savedHistory; + DBG_NAV_LOGD("history.mDidFirstLayout=%s", + params.m_history.didFirstLayout() ? "true" : "false"); + params.m_xMax = xMax; + params.m_yMax = yMax; + params.d.m_keyCode = keyCode; + params.d.m_count = count; + params.d.m_ignoreScroll = ignoreScroll; + m_replay.add(params.d.d, sizeof(params)); + } + } else { + if (visibleRect.intersects(root->focusBounds()) == false) { + setFocusData(root->generation(), 0, 0, 0, 0, true); + sendKitFocus(); // will build cache and retry + } + FirstMoveFocusParams params; + params.d.m_trigger = CommonParams::FirstMoveFocusParams; + params.d.m_generation = m_generation; + params.m_keyCode = keyCode; + params.m_count = count; + params.m_ignoreScroll = ignoreScroll; + m_replay.add(params.d, sizeof(params)); + int docHeight = root->documentHeight(); + int docWidth = root->documentWidth(); + if (visibleRect.bottom() + dy > docHeight) + dy = docHeight - visibleRect.bottom(); + else if (visibleRect.y() + dy < 0) + dy = -visibleRect.y(); + if (visibleRect.right() + dx > docWidth) + dx = docWidth - visibleRect.right(); + else if (visibleRect.x() < 0) + dx = -visibleRect.x(); + result = direction == CachedFrame::LEFT ? dx >= 0 : + direction == CachedFrame::RIGHT ? dx <= 0 : + direction == CachedFrame::UP ? dy >= 0 : dy <= 0; + } + if (focusIsTextArea(DontAllowNewer)) + updateTextEntry(); + else if (oldNodeIsTextArea) + clearTextEntry(); + return result; +} + +void notifyFocusSet(FrameCachePermission inEditingMode) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + // make sure the mFocusData in WebView.java is in sync with WebView.cpp + const CachedFrame* frame = 0; + const CachedNode* node = root->currentFocus(&frame); + const WebCore::IntPoint& focusLocation = root->focusLocation(); + setFocusData(root->generation(), + frame ? (WebCore::Frame*) frame->framePointer() : 0, + node ? (WebCore::Node*) node->nodePointer() : 0, + focusLocation.x(), focusLocation.y(), false); + } + + if (focusIsTextArea(inEditingMode)) + updateTextEntry(); + else if (inEditingMode) + clearTextEntry(); +#if DEBUG_NAV_UI + if (m_frameCacheUI) { + const CachedNode* focus = m_frameCacheUI->currentFocus(); + DBG_NAV_LOGD("focus %d (nativeNode=%p)", + focus ? focus->index() : 0, + focus ? focus->nodePointer() : 0); + } +#endif +} + +void notifyProgressFinished() +{ + DBG_NAV_LOGD("focusIsTextArea=%d", focusIsTextArea(DontAllowNewer)); + updateTextEntry(); +#if DEBUG_NAV_UI + if (m_frameCacheUI) { + const CachedNode* focus = m_frameCacheUI->currentFocus(); + DBG_NAV_LOGD("focus %d (nativeNode=%p)", + focus ? focus->index() : 0, + focus ? focus->nodePointer() : 0); + } +#endif +} + +void recomputeFocus() +{ + int generation; + do { + m_viewImpl->gRecomputeFocusMutex.lock(); + if (!m_viewImpl->m_recomputeEvents.size()) { + m_viewImpl->gRecomputeFocusMutex.unlock(); + return; + } + generation = m_viewImpl->m_recomputeEvents.first(); + m_viewImpl->m_recomputeEvents.remove(0); + m_viewImpl->gRecomputeFocusMutex.unlock(); + DBG_NAV_LOGD("generation=%d", generation); + CachedRoot* root = getFrameCache(AllowNewest); + if (!root) { + DBG_NAV_LOG("!root"); + return; + } + LargestParams storage; + const CommonParams& params = storage.d.d; + char* pos = m_replay.position(); + while (m_replay.retrieve(&storage.d.d) < generation) + DBG_NAV_LOGD("dropped ", params.m_generation); + if (params.m_generation > generation) { + DBG_NAV_LOGD("params.m_generation=%d > generation=%d", + params.m_generation, generation); + m_replay.rewind(pos); + return; + } + int lastAdd = m_replay.lastAdd(); + do { + LOG_ASSERT(params.m_trigger != CommonParams::NoData, "expected data"); + bool inval = generation == m_generation; + switch (params.m_trigger) { + case CommonParams::ClearFocusParams: { + const ClearFocusParams& sParams = *(ClearFocusParams*) &storage; + const CacheParams& cParams = sParams.c; + if (invalidFrame(cParams.m_frame, root)) { + DBG_NAV_LOGD("dropped %s generation=%d", + TriggerNames[params.m_trigger], generation); + return; + } + root->setFocus(cParams.m_frame, cParams.m_node, cParams.m_x, cParams.m_y); + clearFocus(sParams.m_x, sParams.m_y, inval); + DBG_NAV_LOGD("clearFocus(x,y)={%d,%d}", sParams.m_x, sParams.m_y); + } break; + case CommonParams::MotionUpParams: { + const MotionUpParams& mParams = *(MotionUpParams*) &storage; + // const CacheParams& cParams = mParams.c; + // if (invalidFrame(cParams.m_frame, root) == false) + // root->setFocus(cParams.m_frame, cParams.m_node, + // cParams.m_x, cParams.m_y); + motionUp(mParams.m_x, mParams.m_y, mParams.m_slop, mParams.m_isClick, inval, true); + DBG_NAV_LOGD("motionUp m_x=%d m_y=%d", mParams.m_x, mParams.m_y); + } break; + case CommonParams::FirstMoveFocusParams: { + if (invalidFrame((WebCore::Frame*) root->framePointer(), root)) { + DBG_NAV_LOGD("dropped %s generation=%d", + TriggerNames[params.m_trigger], generation); + return; + } + const FirstMoveFocusParams& fParams = *(FirstMoveFocusParams*) &storage; + DBG_NAV_LOGD("first moveFocus keyCode=%d count=%d" + " ignoreScroll=%s", fParams.m_keyCode, fParams.m_count, + fParams.m_ignoreScroll ? "true" : "false"); + moveFocus(fParams.m_keyCode, fParams.m_count, + fParams.m_ignoreScroll, inval, 0, 0); + } break; + case CommonParams::MoveFocusParams: { + const MoveFocusParams& mParams = *(MoveFocusParams*) &storage; + const CacheParams& cParams = mParams.c; + if (invalidFrame(cParams.m_frame, root)) { + DBG_NAV_LOGD("dropped %s generation=%d", + TriggerNames[params.m_trigger], generation); + return; + } + DBG_NAV_LOGD("moveFocus keyCode=%d count=%d ignoreScroll=%s " + "history.mDidFirstLayout=%s", mParams.d.m_keyCode, + mParams.d.m_count, mParams.d.m_ignoreScroll ? "true" : "false", + mParams.m_history.didFirstLayout() ? "true" : "false"); + if (!root->setFocus(cParams.m_frame, cParams.m_node, + cParams.m_x, cParams.m_y)) { + DBG_NAV_LOGD("can't restore focus frame=%p node=%p", + "x=%d y=%d %s", cParams.m_frame, cParams.m_node, + cParams.m_x, cParams.m_y, TriggerNames[params.m_trigger]); + return; + } + root->setVisibleRect(mParams.m_visibleRect); + root->setMaxScroll(mParams.m_xMax, mParams.m_yMax); + *root->rootHistory() = mParams.m_history; + moveFocus(mParams.d.m_keyCode, mParams.d.m_count, + mParams.d.m_ignoreScroll, inval, + mParams.m_sentFocus, &mParams.m_sentBounds); + } break; + default: + LOG_ASSERT(0, "unknown trigger"); + } + if (params.m_generation >= lastAdd) + break; + root = getFrameCache(DontAllowNewer); // re-execution may have retrieved newer cache + m_replay.retrieve(&storage.d.d); + DBG_NAV_LOGD("continuation m_generation %d", params.m_generation); + } while (true); + } while (true); +} + +void resetFocus() +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + CachedRoot* root = getFrameCache(AllowNewer); + if (!root) + return; + root->setCachedFocus(0, 0); +} + +const CachedNode* findAt(CachedRoot* root, const WebCore::IntRect& rect, + const CachedFrame** framePtr, int* rxPtr, int* ryPtr) +{ + *rxPtr = 0; + *ryPtr = 0; + *framePtr = 0; + if (!root) + return 0; + WebCore::IntRect visibleRect; + getVisibleRect(&visibleRect); + root->setVisibleRect(visibleRect); + return root->findAt(rect, framePtr, rxPtr, ryPtr); +} + +void selectBestAt(const WebCore::IntRect& rect) +{ + const CachedFrame* frame; + int rx, ry; + CachedRoot* root = getFrameCache(DontAllowNewer); + const CachedNode* node = findAt(root, rect, &frame, &rx, &ry); + int rootGeneration = root ? root->generation() : 0; + setFocusData(rootGeneration, + frame ? (WebCore::Frame*) frame->framePointer() : 0, + node ? (WebCore::Node*) node->nodePointer() : 0, rx, ry, false); + if (!node) { + DBG_NAV_LOGD("no nodes found root=%p", root); + if (root) { + root->clearFocus(); + root->setCachedFocus(0, 0); + } + sendKitFocus(); + viewInvalidate(); + clearTextEntry(); + return; + } + DBG_NAV_LOGD("CachedNode:%p (%d)", node, node->index()); + const CachedFrame* oldFrame = 0; + const CachedNode* oldFocusNode = root->currentFocus(&oldFrame); + bool oldNodeIsTextArea = focusIsTextArea(DontAllowNewer); + root->setCachedFocus(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(node)); + viewInvalidate(); + if (focusIsTextArea(DontAllowNewer)) + updateTextEntry(); + else if (oldNodeIsTextArea) + clearTextEntry(); +} + +WebCore::IntRect getNavBounds() +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return WebCore::IntRect(0, 0, 0, 0); + return root->rootHistory()->navBounds(); +} + +void setNavBounds(const WebCore::IntRect& rect) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return; + root->rootHistory()->setNavBounds(rect); +} + +void markNodeInvalid(WebCore::Node* node) +{ + DBG_NAV_LOGD("node=%p", node); + m_invalidNode = node; + viewInvalidate(); +} + +bool motionUp(int x, int y, int slop, bool isClick, bool inval, bool retry) +{ + bool pageScrolled = false; + m_followedLink = false; + const CachedFrame* frame; + WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop, slop * 2, slop * 2); + int rx, ry; + CachedRoot* root = getFrameCache(AllowNewer); + const CachedNode* result = findAt(root, rect, &frame, &rx, &ry); + if (!result) { + DBG_NAV_LOGD("no nodes found root=%p", root); + int rootGeneration = 0; + if (root) { + root->clearFocus(); + rootGeneration = root->generation(); + if (!retry) { // scroll first time only + int dx = root->checkForCenter(x, y); + if (dx) { + scrollBy(dx, 0); + retry = true; // don't recompute later since we scrolled + pageScrolled = true; + } + } + } + sendMotionUp(rootGeneration, frame ? + (WebCore::Frame*) frame->framePointer() : 0, + 0, x, y, slop, isClick, retry); + if (inval) + viewInvalidate(); + if (!retry) { + MotionUpParams params; + params.d.m_trigger = CommonParams::MotionUpParams; + params.d.m_generation = m_generation; + params.m_x = x; + params.m_y = y; + params.m_slop = slop; + params.m_isClick = isClick; + m_replay.add(params.d, sizeof(params)); + } + clearTextEntry(); + return pageScrolled; + } + DBG_NAV_LOGD("CachedNode:%p (%d) x=%d y=%d rx=%d ry=%d", result, + result->index(), x, y, rx, ry); + // const CachedFrame* oldFrame = 0; + // const CachedNode* oldFocusNode = root->currentFocus(&oldFrame); + // WebCore::IntPoint focusLocation = root->focusLocation(); + bool oldNodeIsTextArea = !retry && focusIsTextArea(DontAllowNewer); + root->setCachedFocus(const_cast<CachedFrame*>(frame), + const_cast<CachedNode*>(result)); + bool newNodeIsTextArea = focusIsTextArea(DontAllowNewer); + CachedNodeType type = result->type(); + if (type == NORMAL_CACHEDNODETYPE || newNodeIsTextArea) { + sendMotionUp(root->generation(), + frame ? (WebCore::Frame*) frame->framePointer() : 0, + result ? (WebCore::Node*) result->nodePointer() : 0, rx, ry, + slop, isClick, retry); + if (inval) + viewInvalidate(); + if (!retry) { + MotionUpParams params; + params.d.m_trigger = CommonParams::MotionUpParams; + params.d.m_generation = m_generation; + params.m_x = x; + params.m_y = y; + params.m_slop = slop; + params.m_isClick = isClick; + // params.c.setFocus(oldFocusNode, oldFrame, root, focusLocation); + m_replay.add(params.d, sizeof(params)); + } + } else if (inval) + viewInvalidate(); + if (newNodeIsTextArea) { + updateTextEntry(); + displaySoftKeyboard(); + } else { + if (isClick) { + setFollowedLink(true); + if (type != NORMAL_CACHEDNODETYPE) { + overrideUrlLoading(result->getExport()); + } + } + if (oldNodeIsTextArea) + clearTextEntry(); + } + return pageScrolled; +} + +void overrideUrlLoading(const WebCore::String& url) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jName = env->NewString((jchar*) url.characters(), url.length()); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_overrideLoading, jName); + env->DeleteLocalRef(jName); +} + +void setFindIsUp(bool up) +{ + m_viewImpl->m_findIsUp = up; + if (!up) + m_hasCurrentLocation = false; +} + +void setFollowedLink(bool followed) +{ + if ((m_followedLink = followed) != false) { + m_ringAnimationEnd = SkTime::GetMSecs() + 500; + viewInvalidate(); + } +} + +void setHeightCanMeasure(bool measure) +{ + m_heightCanMeasure = measure; +} + +SkIRect m_selStart, m_selEnd; +SkRegion m_selRegion; +#define MIN_ARROW_DISTANCE (20 * 20) + +void moveSelection(int x, int y, bool extendSelection) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return; + const SkPicture& picture = *m_navPictureUI; + WebCore::IntRect r; + getVisibleRect(&r); + SkIRect area; + area.set(r.x(), r.y(), r.right(), r.bottom()); + if (!extendSelection) + m_selStart = m_selEnd = CopyPaste::findClosest(picture, area, x, y); + else + m_selEnd = CopyPaste::findClosest(picture, area, x, y); + DBG_NAV_LOGD("x=%d y=%d extendSelection=%s m_selStart=(%d, %d, %d, %d)" + " m_selEnd=(%d, %d, %d, %d)", x, y, extendSelection ? "true" : "false", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); +} + +const SkRegion& getSelection() +{ + return m_selRegion; +} + +void drawSelection(SkCanvas* canvas, int x, int y, bool extendSelection) +{ + if (!extendSelection) { + int dx = x - m_selStart.fLeft; + dx *= dx; + int otherX = x - m_selStart.fRight; + if (dx > (otherX *= otherX)) + dx = otherX; + int dy = y - m_selStart.fTop; + int dist = dx * dx + dy * dy; + if (dist > MIN_ARROW_DISTANCE) + drawSelectionArrow(canvas, x, y); + else + drawSelectionPointer(canvas, x, y, true); + } else { + drawSelectionRegion(canvas); + drawSelectionPointer(canvas, x, y, false); + } +} + +void drawSelectionRegion(SkCanvas* canvas) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return; + WebCore::IntRect r; + getVisibleRect(&r); + SkIRect area; + area.set(r.x(), r.y(), r.right(), r.bottom()); + m_selRegion.setEmpty(); + CopyPaste::buildSelection(*m_navPictureUI, area, m_selStart, m_selEnd, &m_selRegion); + SkPath path; + m_selRegion.getBoundaryPath(&path); + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(SkColorSetARGB(0x40, 255, 51, 204)); + canvas->drawPath(path, paint); +} + +void drawSelectionPointer(SkCanvas* canvas, int x, int y, bool gridded) +{ + SkPath path; + getSelectionCaret(&path); + SkPaint paint; + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorBLACK); + SkPixelXorXfermode xorMode(SK_ColorWHITE); + paint.setXfermode(&xorMode); + int sc = canvas->save(); + if (gridded) { + bool useLeft = x <= (m_selStart.fLeft + m_selStart.fRight) >> 1; + canvas->translate(SkIntToScalar(useLeft ? m_selStart.fLeft : + m_selStart.fRight), SkIntToScalar(m_selStart.fTop)); + } else + canvas->translate(SkIntToScalar(x), SkIntToScalar(y)); + canvas->drawPath(path, paint); + canvas->restoreToCount(sc); +} + +void drawSelectionArrow(SkCanvas* canvas, int x, int y) +{ + SkPath path; + getSelectionArrow(&path); + SkPaint paint; + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorBLACK); + paint.setStrokeWidth(SK_Scalar1 * 2); + int sc = canvas->save(); + canvas->translate(SkIntToScalar(x), SkIntToScalar(y)); + canvas->drawPath(path, paint); + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(SK_ColorWHITE); + canvas->drawPath(path, paint); + canvas->restoreToCount(sc); +} + +void getSelectionArrow(SkPath* path) +{ + const int arrow[] = { + 0, 14, 3, 11, 5, 15, 9, 15, 7, 11, 11, 11 + }; + for (unsigned index = 0; index < sizeof(arrow)/sizeof(arrow[0]); index += 2) + path->lineTo(SkIntToScalar(arrow[index]), SkIntToScalar(arrow[index + 1])); + path->close(); +} + +void getSelectionCaret(SkPath* path) +{ + SkScalar height = SkIntToScalar(m_selStart.fBottom - m_selStart.fTop); + SkScalar dist = height / 4; + path->lineTo(0, height); + SkScalar bottom = height + dist; + path->lineTo(-dist, bottom); + SkScalar edge = bottom - SK_Scalar1/2; + path->moveTo(-dist, edge); + path->lineTo(dist, edge); + path->moveTo(dist, bottom); + path->lineTo(0, height); +} + +void sendFinalFocus(WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y) +{ + DBG_NAV_LOGD("framePtr=%p nodePtr=%p x=%d y=%d", framePtr, nodePtr, x, y); + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_sendFinalFocus, + (jint) framePtr, (jint) nodePtr, x, y); + checkException(env); +} + +void sendKitFocus() +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_sendKitFocus); + checkException(env); +} + +void sendMotionUp(int buildGeneration, + WebCore::Frame* framePtr, WebCore::Node* nodePtr, int x, int y, int slop, + bool isClick, bool retry) +{ + m_viewImpl->m_touchGeneration = m_viewImpl->m_generation = ++m_generation; + DBG_NAV_LOGD("buildGeneration=%d m_generation=%d framePtr=%p nodePtr=%p" + " x=%d y=%d slop=%d", buildGeneration, + m_generation, framePtr, nodePtr, x, y, slop); + LOG_ASSERT(m_javaGlue.m_obj, "A WebView was not associated with this WebViewNative!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_sendMotionUp, m_generation, + buildGeneration, (jint) framePtr, (jint) nodePtr, x, y, slop, isClick, retry); + checkException(env); +} + +void setFocusData(int buildGeneration, WebCore::Frame* framePtr, + WebCore::Node* nodePtr, int x, int y, bool ignoreNullFocus) +{ + m_viewImpl->m_moveGeneration = m_viewImpl->m_generation = ++m_generation; + DBG_NAV_LOGD("moveGeneration=%d buildGeneration=%d framePtr=%p nodePtr=%p" + " x=%d y=%d", m_generation, buildGeneration, framePtr, nodePtr, x, y); + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_setFocusData, m_generation, + buildGeneration, (jint) framePtr, (jint) nodePtr, x, y, ignoreNullFocus); + checkException(env); +} + +// This function is only used by findNext and setMatches. In it, we store +// upper left corner of the match specified by m_findIndex in +// m_currentMatchLocation. +void inline storeCurrentMatchLocation() +{ + SkASSERT(m_findIndex < m_matches->size()); + const SkIRect& bounds = (*m_matches)[m_findIndex].getLocation().getBounds(); + m_currentMatchLocation.set(bounds.fLeft, bounds.fTop); + m_hasCurrentLocation = true; +} + +void findNext(bool forward) +{ + if (!m_matches || !m_matches->size()) + return; + if (forward) { + m_findIndex++; + if (m_findIndex == m_matches->size()) + m_findIndex = 0; + } else { + if (m_findIndex == 0) { + m_findIndex = m_matches->size() - 1; + } else { + m_findIndex--; + } + } + storeCurrentMatchLocation(); + viewInvalidate(); +} + +// With this call, WebView takes ownership of matches, and is responsible for +// deleting it. +void setMatches(WTF::Vector<MatchInfo>* matches) +{ + if (m_matches) + delete m_matches; + m_matches = matches; + if (m_matches->size()) { + if (m_hasCurrentLocation) { + for (unsigned i = 0; i < m_matches->size(); i++) { + const SkIRect& rect = (*m_matches)[i].getLocation().getBounds(); + if (rect.fLeft == m_currentMatchLocation.fX + && rect.fTop == m_currentMatchLocation.fY) { + m_findIndex = i; + viewInvalidate(); + return; + } + } + } + // If we did not have a stored location, or if we were unable to restore + // it, store the new one. + m_findIndex = 0; + storeCurrentMatchLocation(); + } else { + m_hasCurrentLocation = false; + } + viewInvalidate(); +} + +void scrollBy(int dx, int dy) +{ + LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_scrollBy, + dx, dy, true); + checkException(env); +} + +bool updateFocusNode(JNIEnv* env) +{ + CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) { + DBG_NAV_LOG("!root"); + return false; + } + const CachedFrame* cachedFrame = 0; + const CachedNode* cachedFocusNode = root->currentFocus(&cachedFrame); + if (!cachedFocusNode) { + DBG_NAV_LOG("!cachedFocusNode"); + return false; + } + DBG_NAV_LOGD("cachedFocusNode=%d (nodePointer=%p)", + cachedFocusNode->index(), + cachedFocusNode->nodePointer()); + jobject focusnode = env->GetObjectField(m_javaGlue.object(env).get(), m_javaGlue.m_focusNode); + LOG_ASSERT(focusnode, "Could not find WebView's FocusNode"); + + bool isTextArea = cachedFocusNode->isTextArea(); + bool isTextField = cachedFocusNode->isTextField(); + int maxLength; + jstring jName; + if (isTextField) { + maxLength = cachedFocusNode->maxLength(); + const WebCore::String& name = cachedFocusNode->name(); + jName = env->NewString((jchar*)name.characters(), name.length()); + } else { + maxLength = -1; + jName = 0; + } + WebCore::IntRect bounds = cachedFocusNode->bounds(); + WebCore::String value = cachedFocusNode->getExport(); + jstring val = !value.isEmpty() ? env->NewString((jchar *)value.characters(), value.length()) : 0; + env->CallVoidMethod(focusnode, m_javaGlue.m_setAll, isTextField, isTextArea, cachedFocusNode->isPassword(), + cachedFocusNode->isAnchor(), cachedFocusNode->isRtlText(), maxLength, cachedFocusNode->textSize(), + bounds.x(), bounds.y(), bounds.right(), bounds.bottom(), (int)(cachedFocusNode->nodePointer()), + (int)(cachedFrame->framePointer()), val, jName, root->textGeneration()); + env->DeleteLocalRef(val); + env->DeleteLocalRef(focusnode); + if (isTextField) + env->DeleteLocalRef(jName); + return true; +} + +void updateTextEntry() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_updateTextEntry); + checkException(env); +} + +void displaySoftKeyboard() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), + m_javaGlue.m_displaySoftKeyboard); + checkException(env); +} + +void viewInvalidate() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_viewInvalidate); + checkException(env); +} + +void viewInvalidateRect(int l, int t, int r, int b) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_viewInvalidateRect, l, r, t, b); + checkException(env); +} + +void postInvalidateDelayed(int64_t delay, const WebCore::IntRect& bounds) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue.object(env).get(), m_javaGlue.m_postInvalidateDelayed, + delay, bounds.x(), bounds.y(), bounds.right(), bounds.bottom()); + checkException(env); +} + +private: // local state for WebView + // private to getFrameCache(); other functions operate in a different thread + CachedRoot* m_frameCacheUI; // navigation data ready for use + FocusReplay m_replay; + WebViewCore* m_viewImpl; + WebCore::Node* m_invalidNode; + int m_generation; // associate unique ID with sent kit focus to match with ui + SkPicture* m_navPictureUI; + bool m_followedLink; + SkMSec m_ringAnimationEnd; + // Corresponds to the same-named boolean on the java side. + bool m_heightCanMeasure; + int m_lastDx; + SkMSec m_lastDxTime; + WTF::Vector<MatchInfo>* m_matches; + // Stores the location of the current match. + SkIPoint m_currentMatchLocation; + // Tells whether the value in m_currentMatchLocation is valid. + bool m_hasCurrentLocation; + // Tells whether we have done the setup to draw the Find matches. + bool m_isFindPaintSetUp; + // Paint used to draw our Find matches. + SkPaint m_findPaint; + // Paint used for the background of our Find matches. + SkPaint m_findBlurPaint; + unsigned m_findIndex; +}; // end of WebView class + +/* + * Native JNI methods + */ +static jstring WebCoreStringToJString(JNIEnv *env, WebCore::String string) +{ + int length = string.length(); + if (!length) + return 0; + jstring ret = env->NewString((jchar *)string.characters(), length); + env->DeleteLocalRef(ret); + return ret; +} + +static void nativeClearFocus(JNIEnv *env, jobject obj, int x, int y) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->clearFocus(x, y, true); +} + +static void nativeCreate(JNIEnv *env, jobject obj, int viewImpl) +{ + WebView* webview = new WebView(env, obj, viewImpl); + // NEED THIS OR SOMETHING LIKE IT! + //Release(obj); +} + +static void nativeDebugDump(JNIEnv *env, jobject obj) +{ +#if DUMP_NAV_CACHE + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->debugDump(); +#endif +} + +static void nativeDrawMatches(JNIEnv *env, jobject obj, jobject canv) +{ + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + if (!canv) { + DBG_NAV_LOG("!canv"); + return; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) { + DBG_NAV_LOG("!view"); + return; + } + view->drawMatches(canvas); +} + +static void nativeDrawFocusRing(JNIEnv *env, jobject obj, + jobject canv) +{ + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + if (!canv) { + DBG_NAV_LOG("!canv"); + return; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) { + DBG_NAV_LOG("!view"); + return; + } + view->drawFocusRing(canvas); +} + +static void nativeDrawSelection(JNIEnv *env, jobject obj, + jobject canv, jint x, jint y, bool ex) +{ + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + if (!canv) { + DBG_NAV_LOG("!canv"); + return; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) { + DBG_NAV_LOG("!view"); + return; + } + view->drawSelection(canvas, x, y, ex); +} + +static void nativeDrawSelectionRegion(JNIEnv *env, jobject obj, jobject canv) +{ + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + if (!canv) { + DBG_NAV_LOG("!canv"); + return; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + if (!view) { + DBG_NAV_LOG("!view"); + return; + } + view->drawSelectionRegion(canvas); +} + +static jobject nativeImageURI(JNIEnv *env, jobject obj, jint x, jint y) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WebCore::String uri = view->imageURI(x, y); + jstring ret = 0; + unsigned len = uri.length(); + if (len) { + ret = env->NewString((jchar*) uri.characters(), len); + env->DeleteLocalRef(ret); + } + return ret; +} + +static bool nativeFocusNodeWantsKeyEvents(JNIEnv* env, jobject jwebview) { + WebView* view = GET_NATIVE_VIEW(env, jwebview); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->focusNodeWantsKeyEvents(); +} + +static void nativeInstrumentReport(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounter::reportNow(); +#endif +} + +static WebCore::IntRect jrect_to_webrect(JNIEnv* env, jobject obj) +{ + int L, T, R, B; + GraphicsJNI::get_jrect(env, obj, &L, &T, &R, &B); + return WebCore::IntRect(L, T, R - L, B - T); +} + +static void nativeSelectBestAt(JNIEnv *env, jobject obj, jobject jrect) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WebCore::IntRect rect = jrect_to_webrect(env, jrect); + view->selectBestAt(rect); +} + +static void nativeMarkNodeInvalid(JNIEnv *env, jobject obj, int node) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->markNodeInvalid((WebCore::Node*) node); +} + +static bool nativeMotionUp(JNIEnv *env, jobject obj, + int x, int y, int slop, bool isClick) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->motionUp(x, y, slop, isClick, true, false); +} + +static bool nativeUpdateFocusNode(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->updateFocusNode(env); +} + +static bool nativeMoveFocus(JNIEnv *env, jobject obj, + int key, int count, bool ignoreScroll) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + DBG_NAV_LOGD("env=%p obj=%p view=%p", env, obj, view); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return view->moveFocus(key, count, ignoreScroll, true, 0, 0); +} + +static void nativeNotifyFocusSet(JNIEnv *env, jobject obj, bool inEditingMode) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->notifyFocusSet((WebView::FrameCachePermission) inEditingMode); +} + +static void nativeRecomputeFocus(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->recomputeFocus(); +} + +static void nativeRecordButtons(JNIEnv* env, jobject obj, bool hasFocus, + bool pressed, bool invalidate) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->nativeRecordButtons(hasFocus, pressed, invalidate); +} + +static void nativeResetFocus(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->resetFocus(); +} + +static void nativeSetFindIsDown(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->setFindIsUp(false); +} + +static void nativeSetFollowedLink(JNIEnv *env, jobject obj, bool followed) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->setFollowedLink(followed); +} + +static void nativeSetHeightCanMeasure(JNIEnv *env, jobject obj, bool measure) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeSetHeightCanMeasure"); + view->setHeightCanMeasure(measure); +} + +static jobject nativeGetFocusRingBounds(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + jclass rectClass = env->FindClass("android/graphics/Rect"); + LOG_ASSERT(rectClass, "Could not find Rect class!"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + LOG_ASSERT(init, "Could not find constructor for Rect"); + WebCore::IntRect webRect; + view->focusRingBounds(&webRect); + jobject rect = env->NewObject(rectClass, init, webRect.x(), + webRect.y(), webRect.right(), webRect.bottom()); + return rect; +} + +static jobject nativeGetNavBounds(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + jclass rectClass = env->FindClass("android/graphics/Rect"); + LOG_ASSERT(rectClass, "Could not find Rect class!"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + LOG_ASSERT(init, "Could not find constructor for Rect"); + WebCore::IntRect webRect = view->getNavBounds(); + jobject rect = env->NewObject(rectClass, init, webRect.x(), + webRect.y(), webRect.right(), webRect.bottom()); + return rect; +} + +static void nativeSetNavBounds(JNIEnv *env, jobject obj, jobject jrect) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + WebCore::IntRect rect = jrect_to_webrect(env, jrect); + view->setNavBounds(rect); +} + +static int nativeFindAll(JNIEnv *env, jobject obj, jstring findLower, + jstring findUpper) +{ + // If one or the other is null, do not search. + if (!(findLower && findUpper)) + return 0; + // Obtain the characters for both the lower case string and the upper case + // string representing the same word. + const jchar* findLowerChars = env->GetStringChars(findLower, 0); + const jchar* findUpperChars = env->GetStringChars(findUpper, 0); + // If one or the other is null, do not search. + if (!(findLowerChars && findUpperChars)) { + if (findLowerChars) + env->ReleaseStringChars(findLower, findLowerChars); + if (findUpperChars) + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return 0; + } + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeFindAll"); + view->setFindIsUp(true); + CachedRoot* root = view->getFrameCache(WebView::AllowNewer); + if (!root) { + env->ReleaseStringChars(findLower, findLowerChars); + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return 0; + } + int length = env->GetStringLength(findLower); + // If the lengths of the strings do not match, then they are not the same + // word, so do not search. + if (!length || env->GetStringLength(findUpper) != length) { + env->ReleaseStringChars(findLower, findLowerChars); + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return 0; + } + + int width = root->documentWidth(); + int height = root->documentHeight(); + // Create a FindCanvas, which allows us to fake draw into it so we can + // figure out where our search string is rendered (and how many times). + FindCanvas canvas(width, height, (const UChar*) findLowerChars, + (const UChar*) findUpperChars, length << 1); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + canvas.setBitmapDevice(bitmap); + canvas.drawPicture(*(root->getPicture())); + WTF::Vector<MatchInfo>* matches = canvas.detachMatches(); + // With setMatches, the WebView takes ownership of matches + view->setMatches(matches); + + env->ReleaseStringChars(findLower, findLowerChars); + env->ReleaseStringChars(findUpper, findUpperChars); + checkException(env); + return canvas.found(); +} + +static void nativeFindNext(JNIEnv *env, jobject obj, bool forward) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeFindNext"); + view->findNext(forward); +} + +static void nativeUpdateCachedTextfield(JNIEnv *env, jobject obj, jstring updatedText, jint generation) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeUpdateCachedTextfield"); + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (!root) + return; + const CachedNode* cachedFocusNode = root->currentFocus(); + if (!cachedFocusNode || (!cachedFocusNode->isTextField() && !cachedFocusNode->isTextArea())) + return; + WebCore::String webcoreString = to_string(env, updatedText); + (const_cast<CachedNode*>(cachedFocusNode))->setExport(webcoreString); + root->setTextGeneration(generation); + checkException(env); +} + +static void nativeDestroy(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOGD("nativeDestroy view: %p", view); + LOG_ASSERT(view, "view not set in nativeDestroy"); + delete view; +} + +static void nativeMoveSelection(JNIEnv *env, jobject obj, int x, int y, bool ex) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + view->moveSelection(x, y, ex); +} + +static jobject nativeGetSelection(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + return GraphicsJNI::createRegion(env, new SkRegion(view->getSelection())); +} + +#ifdef ANDROID_DUMP_DISPLAY_TREE +static void dumpToFile(const char text[], void* file) { + fwrite(text, 1, strlen(text), reinterpret_cast<FILE*>(file)); + fwrite("\n", 1, 1, reinterpret_cast<FILE*>(file)); +} +#endif + +static void nativeDumpDisplayTree(JNIEnv* env, jobject jwebview, jstring jurl) +{ +#ifdef ANDROID_DUMP_DISPLAY_TREE + WebView* view = GET_NATIVE_VIEW(env, jwebview); + LOG_ASSERT(view, "view not set in %s", __FUNCTION__); + + CachedRoot* root = view->getFrameCache(WebView::DontAllowNewer); + if (root) { + SkPicture* picture = root->getPicture(); + if (picture) { + FILE* file = fopen(DISPLAY_TREE_LOG_FILE, "w"); + if (file) { + SkFormatDumper dumper(dumpToFile, file); + // dump the URL + if (jurl) { + const char* str = env->GetStringUTFChars(jurl, 0); + SkDebugf("Dumping %s to %s\n", str, DISPLAY_TREE_LOG_FILE); + dumpToFile(str, file); + env->ReleaseStringUTFChars(jurl, str); + } + // now dump the display tree + SkDumpCanvas canvas(&dumper); + // this will playback the picture into the canvas, which will + // spew its contents to the dumper + picture->draw(&canvas); + // we're done with the file now + fwrite("\n", 1, 1, file); + fclose(file); + } + } + } +#endif +} + +/* + * JNI registration + */ +static JNINativeMethod gJavaWebViewMethods[] = { + { "nativeFindAll", "(Ljava/lang/String;Ljava/lang/String;)I", + (void*) nativeFindAll }, + { "nativeFindNext", "(Z)V", + (void*) nativeFindNext }, + { "nativeClearFocus", "(II)V", + (void*) nativeClearFocus }, + { "nativeCreate", "(I)V", + (void*) nativeCreate }, + { "nativeDebugDump", "()V", + (void*) nativeDebugDump }, + { "nativeDestroy", "()V", + (void*) nativeDestroy }, + { "nativeDrawMatches", "(Landroid/graphics/Canvas;)V", + (void*) nativeDrawMatches }, + { "nativeDrawFocusRing", "(Landroid/graphics/Canvas;)V", + (void*) nativeDrawFocusRing }, + { "nativeDrawSelection", "(Landroid/graphics/Canvas;IIZ)V", + (void*) nativeDrawSelection }, + { "nativeDrawSelectionRegion", "(Landroid/graphics/Canvas;)V", + (void*) nativeDrawSelectionRegion }, + { "nativeUpdateFocusNode", "()Z", + (void*) nativeUpdateFocusNode }, + { "nativeGetFocusRingBounds", "()Landroid/graphics/Rect;", + (void*) nativeGetFocusRingBounds }, + { "nativeGetNavBounds", "()Landroid/graphics/Rect;", + (void*) nativeGetNavBounds }, + { "nativeInstrumentReport", "()V", + (void*) nativeInstrumentReport }, + { "nativeMarkNodeInvalid", "(I)V", + (void*) nativeMarkNodeInvalid }, + { "nativeMotionUp", "(IIIZ)Z", + (void*) nativeMotionUp }, + { "nativeMoveFocus", "(IIZ)Z", + (void*) nativeMoveFocus }, + { "nativeNotifyFocusSet", "(Z)V", + (void*) nativeNotifyFocusSet }, + { "nativeRecomputeFocus", "()V", + (void*) nativeRecomputeFocus }, + { "nativeRecordButtons", "(ZZZ)V", + (void*) nativeRecordButtons }, + { "nativeResetFocus", "()V", + (void*) nativeResetFocus }, + { "nativeSelectBestAt", "(Landroid/graphics/Rect;)V", + (void*) nativeSelectBestAt }, + { "nativeSetFindIsDown", "()V", + (void*) nativeSetFindIsDown }, + { "nativeSetFollowedLink", "(Z)V", + (void*) nativeSetFollowedLink }, + { "nativeSetHeightCanMeasure", "(Z)V", + (void*) nativeSetHeightCanMeasure }, + { "nativeSetNavBounds", "(Landroid/graphics/Rect;)V", + (void*) nativeSetNavBounds }, + { "nativeImageURI", "(II)Ljava/lang/String;", + (void*) nativeImageURI }, + { "nativeFocusNodeWantsKeyEvents", "()Z", + (void*)nativeFocusNodeWantsKeyEvents }, + { "nativeUpdateCachedTextfield", "(Ljava/lang/String;I)V", + (void*) nativeUpdateCachedTextfield }, + { "nativeMoveSelection", "(IIZ)V", + (void*) nativeMoveSelection }, + { "nativeGetSelection", "()Landroid/graphics/Region;", + (void*) nativeGetSelection }, + { "nativeDumpDisplayTree", "(Ljava/lang/String;)V", + (void*) nativeDumpDisplayTree } +}; + +int register_webview(JNIEnv* env) +{ + jclass clazz = env->FindClass("android/webkit/WebView"); + LOG_ASSERT(clazz, "Unable to find class android/webkit/WebView"); + gWebViewField = env->GetFieldID(clazz, "mNativeClass", "I"); + LOG_ASSERT(gWebViewField, "Unable to find android/webkit/WebView.mNativeClass"); + + return jniRegisterNativeMethods(env, "android/webkit/WebView", gJavaWebViewMethods, NELEM(gJavaWebViewMethods)); +} + +} // namespace android |