| /* |
| * 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; |
| 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; |
| } |
| } |
| 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() < MINIMUM_FOCUSABLE_WIDTH) |
| continue; |
| if (bounds.height() < MINIMUM_FOCUSABLE_HEIGHT) |
| 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); |
| } |
| |
| } |