1/* 2 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) 4 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28#import "config.h" 29#import "Frame.h" 30 31#import "BlockExceptions.h" 32#import "ColorMac.h" 33#import "Cursor.h" 34#import "DOMInternal.h" 35#import "Event.h" 36#import "FrameLoaderClient.h" 37#import "FrameView.h" 38#import "GraphicsContext.h" 39#import "HTMLNames.h" 40#import "HTMLTableCellElement.h" 41#import "HitTestRequest.h" 42#import "HitTestResult.h" 43#import "KeyboardEvent.h" 44#import "Logging.h" 45#import "MouseEventWithHitTestResults.h" 46#import "Page.h" 47#import "PlatformKeyboardEvent.h" 48#import "PlatformWheelEvent.h" 49#import "RegularExpression.h" 50#import "RenderTableCell.h" 51#import "Scrollbar.h" 52#import "SimpleFontData.h" 53#import "WebCoreViewFactory.h" 54#import "visible_units.h" 55#import <Carbon/Carbon.h> 56#import <wtf/StdLibExtras.h> 57 58@interface NSView (WebCoreHTMLDocumentView) 59- (void)drawSingleRect:(NSRect)rect; 60@end 61 62using namespace std; 63 64namespace WebCore { 65 66using namespace HTMLNames; 67 68// Either get cached regexp or build one that matches any of the labels. 69// The regexp we build is of the form: (STR1|STR2|STRN) 70static RegularExpression* regExpForLabels(NSArray* labels) 71{ 72 // All the ObjC calls in this method are simple array and string 73 // calls which we can assume do not raise exceptions 74 75 76 // Parallel arrays that we use to cache regExps. In practice the number of expressions 77 // that the app will use is equal to the number of locales is used in searching. 78 static const unsigned int regExpCacheSize = 4; 79 static NSMutableArray* regExpLabels = nil; 80 DEFINE_STATIC_LOCAL(Vector<RegularExpression*>, regExps, ()); 81 DEFINE_STATIC_LOCAL(RegularExpression, wordRegExp, ("\\w", TextCaseSensitive)); 82 83 RegularExpression* result; 84 if (!regExpLabels) 85 regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize]; 86 CFIndex cacheHit = [regExpLabels indexOfObject:labels]; 87 if (cacheHit != NSNotFound) 88 result = regExps.at(cacheHit); 89 else { 90 String pattern("("); 91 unsigned int numLabels = [labels count]; 92 unsigned int i; 93 for (i = 0; i < numLabels; i++) { 94 String label = [labels objectAtIndex:i]; 95 96 bool startsWithWordChar = false; 97 bool endsWithWordChar = false; 98 if (label.length() != 0) { 99 startsWithWordChar = wordRegExp.match(label.substring(0, 1)) >= 0; 100 endsWithWordChar = wordRegExp.match(label.substring(label.length() - 1, 1)) >= 0; 101 } 102 103 if (i != 0) 104 pattern.append("|"); 105 // Search for word boundaries only if label starts/ends with "word characters". 106 // If we always searched for word boundaries, this wouldn't work for languages 107 // such as Japanese. 108 if (startsWithWordChar) 109 pattern.append("\\b"); 110 pattern.append(label); 111 if (endsWithWordChar) 112 pattern.append("\\b"); 113 } 114 pattern.append(")"); 115 result = new RegularExpression(pattern, TextCaseInsensitive); 116 } 117 118 // add regexp to the cache, making sure it is at the front for LRU ordering 119 if (cacheHit != 0) { 120 if (cacheHit != NSNotFound) { 121 // remove from old spot 122 [regExpLabels removeObjectAtIndex:cacheHit]; 123 regExps.remove(cacheHit); 124 } 125 // add to start 126 [regExpLabels insertObject:labels atIndex:0]; 127 regExps.insert(0, result); 128 // trim if too big 129 if ([regExpLabels count] > regExpCacheSize) { 130 [regExpLabels removeObjectAtIndex:regExpCacheSize]; 131 RegularExpression* last = regExps.last(); 132 regExps.removeLast(); 133 delete last; 134 } 135 } 136 return result; 137} 138 139NSString* Frame::searchForLabelsBeforeElement(NSArray* labels, Element* element, size_t* resultDistance, bool* resultIsInCellAbove) 140{ 141 RegularExpression* regExp = regExpForLabels(labels); 142 // We stop searching after we've seen this many chars 143 const unsigned int charsSearchedThreshold = 500; 144 // This is the absolute max we search. We allow a little more slop than 145 // charsSearchedThreshold, to make it more likely that we'll search whole nodes. 146 const unsigned int maxCharsSearched = 600; 147 // If the starting element is within a table, the cell that contains it 148 HTMLTableCellElement* startingTableCell = 0; 149 bool searchedCellAbove = false; 150 151 if (resultDistance) 152 *resultDistance = notFound; 153 if (resultIsInCellAbove) 154 *resultIsInCellAbove = false; 155 156 // walk backwards in the node tree, until another element, or form, or end of tree 157 unsigned lengthSearched = 0; 158 Node* n; 159 for (n = element->traversePreviousNode(); 160 n && lengthSearched < charsSearchedThreshold; 161 n = n->traversePreviousNode()) 162 { 163 if (n->hasTagName(formTag) 164 || (n->isHTMLElement() && static_cast<Element*>(n)->isFormControlElement())) 165 { 166 // We hit another form element or the start of the form - bail out 167 break; 168 } else if (n->hasTagName(tdTag) && !startingTableCell) { 169 startingTableCell = static_cast<HTMLTableCellElement*>(n); 170 } else if (n->hasTagName(trTag) && startingTableCell) { 171 NSString* result = searchForLabelsAboveCell(regExp, startingTableCell, resultDistance); 172 if (result && [result length] > 0) { 173 if (resultIsInCellAbove) 174 *resultIsInCellAbove = true; 175 return result; 176 } 177 searchedCellAbove = true; 178 } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { 179 // For each text chunk, run the regexp 180 String nodeString = n->nodeValue(); 181 // add 100 for slop, to make it more likely that we'll search whole nodes 182 if (lengthSearched + nodeString.length() > maxCharsSearched) 183 nodeString = nodeString.right(charsSearchedThreshold - lengthSearched); 184 int pos = regExp->searchRev(nodeString); 185 if (pos >= 0) { 186 if (resultDistance) 187 *resultDistance = lengthSearched; 188 return nodeString.substring(pos, regExp->matchedLength()); 189 } 190 lengthSearched += nodeString.length(); 191 } 192 } 193 194 // If we started in a cell, but bailed because we found the start of the form or the 195 // previous element, we still might need to search the row above us for a label. 196 if (startingTableCell && !searchedCellAbove) { 197 NSString* result = searchForLabelsAboveCell(regExp, startingTableCell, resultDistance); 198 if (result && [result length] > 0) { 199 if (resultIsInCellAbove) 200 *resultIsInCellAbove = true; 201 return result; 202 } 203 } 204 205 return nil; 206} 207 208static NSString *matchLabelsAgainstString(NSArray *labels, const String& stringToMatch) 209{ 210 if (stringToMatch.isEmpty()) 211 return nil; 212 213 String mutableStringToMatch = stringToMatch; 214 215 // Make numbers and _'s in field names behave like word boundaries, e.g., "address2" 216 replace(mutableStringToMatch, RegularExpression("\\d", TextCaseSensitive), " "); 217 mutableStringToMatch.replace('_', ' '); 218 219 RegularExpression* regExp = regExpForLabels(labels); 220 // Use the largest match we can find in the whole string 221 int pos; 222 int length; 223 int bestPos = -1; 224 int bestLength = -1; 225 int start = 0; 226 do { 227 pos = regExp->match(mutableStringToMatch, start); 228 if (pos != -1) { 229 length = regExp->matchedLength(); 230 if (length >= bestLength) { 231 bestPos = pos; 232 bestLength = length; 233 } 234 start = pos + 1; 235 } 236 } while (pos != -1); 237 238 if (bestPos != -1) 239 return mutableStringToMatch.substring(bestPos, bestLength); 240 return nil; 241} 242 243NSString* Frame::matchLabelsAgainstElement(NSArray* labels, Element* element) 244{ 245 // Match against the name element, then against the id element if no match is found for the name element. 246 // See 7538330 for one popular site that benefits from the id element check. 247 // FIXME: This code is mirrored in Frame.cpp. It would be nice to make the Mac code call the platform-agnostic 248 // code, which would require converting the NSArray of NSStrings to a Vector of Strings somewhere along the way. 249 String resultFromNameAttribute = matchLabelsAgainstString(labels, element->getAttribute(nameAttr)); 250 if (!resultFromNameAttribute.isEmpty()) 251 return resultFromNameAttribute; 252 253 return matchLabelsAgainstString(labels, element->getAttribute(idAttr)); 254} 255 256NSImage* Frame::imageFromRect(NSRect rect) const 257{ 258 PaintBehavior oldBehavior = m_view->paintBehavior(); 259 m_view->setPaintBehavior(oldBehavior | PaintBehaviorFlattenCompositingLayers); 260 261 BEGIN_BLOCK_OBJC_EXCEPTIONS; 262 263 NSImage* resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease]; 264 265 if (rect.size.width != 0 && rect.size.height != 0) { 266 [resultImage setFlipped:YES]; 267 [resultImage lockFocus]; 268 269 GraphicsContext graphicsContext((CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]); 270 graphicsContext.save(); 271 graphicsContext.translate(-rect.origin.x, -rect.origin.y); 272 m_view->paintContents(&graphicsContext, IntRect(rect)); 273 graphicsContext.restore(); 274 275 [resultImage unlockFocus]; 276 [resultImage setFlipped:NO]; 277 } 278 279 m_view->setPaintBehavior(oldBehavior); 280 return resultImage; 281 282 END_BLOCK_OBJC_EXCEPTIONS; 283 284 m_view->setPaintBehavior(oldBehavior); 285 return nil; 286} 287 288NSImage* Frame::selectionImage(bool forceBlackText) const 289{ 290 m_view->setPaintBehavior(PaintBehaviorSelectionOnly | (forceBlackText ? PaintBehaviorForceBlackText : 0)); 291 m_doc->updateLayout(); 292 NSImage* result = imageFromRect(selection()->bounds()); 293 m_view->setPaintBehavior(PaintBehaviorNormal); 294 return result; 295} 296 297NSImage* Frame::snapshotDragImage(Node* node, NSRect* imageRect, NSRect* elementRect) const 298{ 299 RenderObject* renderer = node->renderer(); 300 if (!renderer) 301 return nil; 302 303 renderer->updateDragState(true); // mark dragged nodes (so they pick up the right CSS) 304 m_doc->updateLayout(); // forces style recalc - needed since changing the drag state might 305 // imply new styles, plus JS could have changed other things 306 IntRect topLevelRect; 307 NSRect paintingRect = renderer->paintingRootRect(topLevelRect); 308 309 m_view->setNodeToDraw(node); // invoke special sub-tree drawing mode 310 NSImage* result = imageFromRect(paintingRect); 311 renderer->updateDragState(false); 312 m_doc->updateLayout(); 313 m_view->setNodeToDraw(0); 314 315 if (elementRect) 316 *elementRect = topLevelRect; 317 if (imageRect) 318 *imageRect = paintingRect; 319 return result; 320} 321 322DragImageRef Frame::nodeImage(Node* node) 323{ 324 RenderObject* renderer = node->renderer(); 325 if (!renderer) 326 return nil; 327 328 m_doc->updateLayout(); // forces style recalc 329 330 IntRect topLevelRect; 331 NSRect paintingRect = renderer->paintingRootRect(topLevelRect); 332 333 m_view->setNodeToDraw(node); // invoke special sub-tree drawing mode 334 NSImage* result = imageFromRect(paintingRect); 335 m_view->setNodeToDraw(0); 336 337 return result; 338} 339 340DragImageRef Frame::dragImageForSelection() 341{ 342 if (!selection()->isRange()) 343 return nil; 344 return selectionImage(); 345} 346 347} // namespace WebCore 348