/* * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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 APPLE COMPUTER, INC. ``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. */ #import "config.h" #import "Frame.h" #import "BlockExceptions.h" #import "ColorMac.h" #import "Cursor.h" #import "DOMInternal.h" #import "DocumentLoader.h" #import "EditorClient.h" #import "Event.h" #import "FrameLoaderClient.h" #import "FrameView.h" #import "GraphicsContext.h" #import "HTMLNames.h" #import "HTMLTableCellElement.h" #import "HitTestRequest.h" #import "HitTestResult.h" #import "KeyboardEvent.h" #import "Logging.h" #import "MouseEventWithHitTestResults.h" #import "Page.h" #import "PlatformKeyboardEvent.h" #import "PlatformWheelEvent.h" #import "RegularExpression.h" #import "RenderTableCell.h" #import "Scrollbar.h" #import "SimpleFontData.h" #import "WebCoreViewFactory.h" #import "visible_units.h" #import <wtf/StdLibExtras.h> #if ENABLE(DASHBOARD_SUPPORT) #import "WebDashboardRegion.h" #endif #import "EventListener.h" #import <wtf/GetPtr.h> #import <wtf/UnusedParam.h> #ifndef NSView #define NSView WAKView #endif #import "WAKView.h" @interface NSView (WebCoreHTMLDocumentView) - (void)drawSingleRect:(NSRect)rect; @end using namespace std; namespace WebCore { using namespace HTMLNames; // Either get cached regexp or build one that matches any of the labels. // The regexp we build is of the form: (STR1|STR2|STRN) static RegularExpression* regExpForLabels(NSArray* labels) { // All the ObjC calls in this method are simple array and string // calls which we can assume do not raise exceptions // Parallel arrays that we use to cache regExps. In practice the number of expressions // that the app will use is equal to the number of locales is used in searching. static const unsigned int regExpCacheSize = 4; static NSMutableArray* regExpLabels = nil; DEFINE_STATIC_LOCAL(Vector<RegularExpression*>, regExps, ()); DEFINE_STATIC_LOCAL(RegularExpression, wordRegExp, ("\\w", TextCaseSensitive)); RegularExpression* result; if (!regExpLabels) regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize]; CFIndex cacheHit = [regExpLabels indexOfObject:labels]; if (cacheHit != NSNotFound) result = regExps.at(cacheHit); else { String pattern("("); unsigned int numLabels = [labels count]; unsigned int i; for (i = 0; i < numLabels; i++) { String label = [labels objectAtIndex:i]; bool startsWithWordChar = false; bool endsWithWordChar = false; if (label.length() != 0) { startsWithWordChar = wordRegExp.match(label.substring(0, 1)) >= 0; endsWithWordChar = wordRegExp.match(label.substring(label.length() - 1, 1)) >= 0; } if (i != 0) pattern.append("|"); // Search for word boundaries only if label starts/ends with "word characters". // If we always searched for word boundaries, this wouldn't work for languages // such as Japanese. if (startsWithWordChar) pattern.append("\\b"); pattern.append(label); if (endsWithWordChar) pattern.append("\\b"); } pattern.append(")"); result = new RegularExpression(pattern, TextCaseInsensitive); } // add regexp to the cache, making sure it is at the front for LRU ordering if (cacheHit != 0) { if (cacheHit != NSNotFound) { // remove from old spot [regExpLabels removeObjectAtIndex:cacheHit]; regExps.remove(cacheHit); } // add to start [regExpLabels insertObject:labels atIndex:0]; regExps.insert(0, result); // trim if too big if ([regExpLabels count] > regExpCacheSize) { [regExpLabels removeObjectAtIndex:regExpCacheSize]; RegularExpression* last = regExps.last(); regExps.removeLast(); delete last; } } return result; } NSString* Frame::searchForNSLabelsAboveCell(RegularExpression* regExp, HTMLTableCellElement* cell, size_t* resultDistanceFromStartOfCell) { RenderObject* cellRenderer = cell->renderer(); if (cellRenderer && cellRenderer->isTableCell()) { RenderTableCell* tableCellRenderer = toRenderTableCell(cellRenderer); RenderTableCell* cellAboveRenderer = tableCellRenderer->table()->cellAbove(tableCellRenderer); if (cellAboveRenderer) { HTMLTableCellElement* aboveCell = static_cast<HTMLTableCellElement*>(cellAboveRenderer->node()); if (aboveCell) { // search within the above cell we found for a match size_t lengthSearched = 0; for (Node* n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) { if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { // For each text chunk, run the regexp String nodeString = n->nodeValue(); int pos = regExp->searchRev(nodeString); if (pos >= 0) { if (resultDistanceFromStartOfCell) *resultDistanceFromStartOfCell = lengthSearched; return nodeString.substring(pos, regExp->matchedLength()); } lengthSearched += nodeString.length(); } } } } } // Any reason in practice to search all cells in that are above cell? if (resultDistanceFromStartOfCell) *resultDistanceFromStartOfCell = notFound; return nil; } NSString* Frame::searchForLabelsBeforeElement(NSArray* labels, Element* element, size_t* resultDistance, bool* resultIsInCellAbove) { RegularExpression* regExp = regExpForLabels(labels); // We stop searching after we've seen this many chars const unsigned int charsSearchedThreshold = 500; // This is the absolute max we search. We allow a little more slop than // charsSearchedThreshold, to make it more likely that we'll search whole nodes. const unsigned int maxCharsSearched = 600; // If the starting element is within a table, the cell that contains it HTMLTableCellElement* startingTableCell = 0; bool searchedCellAbove = false; if (resultDistance) *resultDistance = notFound; if (resultIsInCellAbove) *resultIsInCellAbove = false; // walk backwards in the node tree, until another element, or form, or end of tree int unsigned lengthSearched = 0; Node* n; for (n = element->traversePreviousNode(); n && lengthSearched < charsSearchedThreshold; n = n->traversePreviousNode()) { if (n->hasTagName(formTag) || (n->isHTMLElement() && static_cast<Element*>(n)->isFormControlElement())) { // We hit another form element or the start of the form - bail out break; } else if (n->hasTagName(tdTag) && !startingTableCell) { startingTableCell = static_cast<HTMLTableCellElement*>(n); } else if (n->hasTagName(trTag) && startingTableCell) { NSString* result = searchForLabelsAboveCell(regExp, startingTableCell, resultDistance); if (result && [result length] > 0) { if (resultIsInCellAbove) *resultIsInCellAbove = true; return result; } searchedCellAbove = true; } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { // For each text chunk, run the regexp String nodeString = n->nodeValue(); // add 100 for slop, to make it more likely that we'll search whole nodes if (lengthSearched + nodeString.length() > maxCharsSearched) nodeString = nodeString.right(charsSearchedThreshold - lengthSearched); int pos = regExp->searchRev(nodeString); if (pos >= 0) { if (resultDistance) *resultDistance = lengthSearched; return nodeString.substring(pos, regExp->matchedLength()); } lengthSearched += nodeString.length(); } } // If we started in a cell, but bailed because we found the start of the form or the // previous element, we still might need to search the row above us for a label. if (startingTableCell && !searchedCellAbove) { NSString* result = searchForLabelsAboveCell(regExp, startingTableCell, resultDistance); if (result && [result length] > 0) { if (resultIsInCellAbove) *resultIsInCellAbove = true; return result; } } return nil; } static NSString *matchLabelsAgainstString(NSArray *labels, const String& stringToMatch) { if (stringToMatch.isEmpty()) return nil; String mutableStringToMatch = stringToMatch; // Make numbers and _'s in field names behave like word boundaries, e.g., "address2" replace(mutableStringToMatch, RegularExpression("\\d", TextCaseSensitive), " "); mutableStringToMatch.replace('_', ' '); RegularExpression* regExp = regExpForLabels(labels); // Use the largest match we can find in the whole string int pos; int length; int bestPos = -1; int bestLength = -1; int start = 0; do { pos = regExp->match(mutableStringToMatch, start); if (pos != -1) { length = regExp->matchedLength(); if (length >= bestLength) { bestPos = pos; bestLength = length; } start = pos + 1; } } while (pos != -1); if (bestPos != -1) return mutableStringToMatch.substring(bestPos, bestLength); return nil; } NSString* Frame::matchLabelsAgainstElement(NSArray* labels, Element* element) { // Match against the name element, then against the id element if no match is found for the name element. // See 7538330 for one popular site that benefits from the id element check. // FIXME: This code is mirrored in Frame.cpp. It would be nice to make the Mac code call the platform-agnostic // code, which would require converting the NSArray of NSStrings to a Vector of Strings somewhere along the way. String resultFromNameAttribute = matchLabelsAgainstString(labels, element->getAttribute(nameAttr)); if (!resultFromNameAttribute.isEmpty()) return resultFromNameAttribute; return matchLabelsAgainstString(labels, element->getAttribute(idAttr)); } NSDictionary* Frame::fontAttributesForSelectionStart() const { Node* nodeToRemove; RenderStyle* style = styleForSelectionStart(nodeToRemove); if (!style) return nil; NSMutableDictionary* result = [NSMutableDictionary dictionary]; return result; } NSWritingDirection Frame::baseWritingDirectionForSelectionStart() const { // Convert WebCore's WritingDirection into an NSWritingDirection. WritingDirection direction = editor()->baseWritingDirectionForSelectionStart(); ASSERT(direction != NaturalWritingDirection); return direction == LeftToRightWritingDirection ? NSWritingDirectionLeftToRight : NSWritingDirectionRightToLeft; } #if ENABLE(DASHBOARD_SUPPORT) NSMutableDictionary* Frame::dashboardRegionsDictionary() { Document* doc = document(); const Vector<DashboardRegionValue>& regions = doc->dashboardRegions(); size_t n = regions.size(); // Convert the Vector<DashboardRegionValue> into a NSDictionary of WebDashboardRegions NSMutableDictionary* webRegions = [NSMutableDictionary dictionaryWithCapacity:n]; for (size_t i = 0; i < n; i++) { const DashboardRegionValue& region = regions[i]; if (region.type == StyleDashboardRegion::None) continue; NSString *label = region.label; WebDashboardRegionType type = WebDashboardRegionTypeNone; if (region.type == StyleDashboardRegion::Circle) type = WebDashboardRegionTypeCircle; else if (region.type == StyleDashboardRegion::Rectangle) type = WebDashboardRegionTypeRectangle; NSMutableArray *regionValues = [webRegions objectForKey:label]; if (!regionValues) { regionValues = [[NSMutableArray alloc] initWithCapacity:1]; [webRegions setObject:regionValues forKey:label]; [regionValues release]; } WebDashboardRegion *webRegion = [[WebDashboardRegion alloc] initWithRect:region.bounds clip:region.clip type:type]; [regionValues addObject:webRegion]; [webRegion release]; } return webRegions; } #endif DragImageRef Frame::dragImageForSelection() { return nil; } #define NSFloatValue(aFloat) [NSNumber numberWithFloat:aFloat] NSDictionary* Frame::dictionaryForViewportArguments(const ViewportArguments& arguments) const { return [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:NSFloatValue(arguments.initialScale), NSFloatValue(arguments.minimumScale), NSFloatValue(arguments.maximumScale), NSFloatValue(arguments.userScalable), NSFloatValue(arguments.width), NSFloatValue(arguments.height), nil] forKeys:[NSArray arrayWithObjects:@"initial-scale", @"minimum-scale", @"maximum-scale", @"user-scalable", @"width", @"height", nil]]; } const ViewportArguments& Frame::viewportArguments() const { return m_viewportArguments; } void Frame::setViewportArguments(const ViewportArguments& arguments) { m_viewportArguments = arguments; } static int sAllLayouts = 0; void Frame::didParse(double duration) { m_parseCount++; m_parseDuration += duration; } void Frame::didLayout(bool /*firstLayout*/, double duration) { sAllLayouts++; m_layoutCount++; m_layoutDuration += duration; } void Frame::didForcedLayout() { m_forcedLayoutCount++; } void Frame::getPPTStats(unsigned& parseCount, unsigned& layoutCount, unsigned& forcedLayoutCount, CFTimeInterval& parseDuration, CFTimeInterval& layoutDuration) { parseCount = m_parseCount; layoutCount = m_layoutCount; forcedLayoutCount = m_forcedLayoutCount; parseDuration = m_parseDuration; layoutDuration = m_layoutDuration; //fprintf(stderr, "All Layouts: %d\n", sAllLayouts); } void Frame::clearPPTStats() { m_parseCount = 0; m_layoutCount = 0; m_forcedLayoutCount = 0; m_parseDuration = 0.0; m_layoutDuration = 0.0; } } // namespace WebCore