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 "DocumentLoader.h" 36#import "EditorClient.h" 37#import "Event.h" 38#import "FrameLoaderClient.h" 39#import "FrameView.h" 40#import "GraphicsContext.h" 41#import "HTMLNames.h" 42#import "HTMLTableCellElement.h" 43#import "HitTestRequest.h" 44#import "HitTestResult.h" 45#import "KeyboardEvent.h" 46#import "Logging.h" 47#import "MouseEventWithHitTestResults.h" 48#import "Page.h" 49#import "PlatformKeyboardEvent.h" 50#import "PlatformWheelEvent.h" 51#import "RegularExpression.h" 52#import "RenderTableCell.h" 53#import "Scrollbar.h" 54#import "SimpleFontData.h" 55#import "WebCoreViewFactory.h" 56#import "visible_units.h" 57 58#import <Carbon/Carbon.h> 59#import <wtf/StdLibExtras.h> 60 61#if ENABLE(DASHBOARD_SUPPORT) 62#import "WebDashboardRegion.h" 63#endif 64 65@interface NSView (WebCoreHTMLDocumentView) 66- (void)drawSingleRect:(NSRect)rect; 67@end 68 69using namespace std; 70 71namespace WebCore { 72 73using namespace HTMLNames; 74 75// Either get cached regexp or build one that matches any of the labels. 76// The regexp we build is of the form: (STR1|STR2|STRN) 77static RegularExpression* regExpForLabels(NSArray* labels) 78{ 79 // All the ObjC calls in this method are simple array and string 80 // calls which we can assume do not raise exceptions 81 82 83 // Parallel arrays that we use to cache regExps. In practice the number of expressions 84 // that the app will use is equal to the number of locales is used in searching. 85 static const unsigned int regExpCacheSize = 4; 86 static NSMutableArray* regExpLabels = nil; 87 DEFINE_STATIC_LOCAL(Vector<RegularExpression*>, regExps, ()); 88 DEFINE_STATIC_LOCAL(RegularExpression, wordRegExp, ("\\w", TextCaseSensitive)); 89 90 RegularExpression* result; 91 if (!regExpLabels) 92 regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize]; 93 CFIndex cacheHit = [regExpLabels indexOfObject:labels]; 94 if (cacheHit != NSNotFound) 95 result = regExps.at(cacheHit); 96 else { 97 String pattern("("); 98 unsigned int numLabels = [labels count]; 99 unsigned int i; 100 for (i = 0; i < numLabels; i++) { 101 String label = [labels objectAtIndex:i]; 102 103 bool startsWithWordChar = false; 104 bool endsWithWordChar = false; 105 if (label.length() != 0) { 106 startsWithWordChar = wordRegExp.match(label.substring(0, 1)) >= 0; 107 endsWithWordChar = wordRegExp.match(label.substring(label.length() - 1, 1)) >= 0; 108 } 109 110 if (i != 0) 111 pattern.append("|"); 112 // Search for word boundaries only if label starts/ends with "word characters". 113 // If we always searched for word boundaries, this wouldn't work for languages 114 // such as Japanese. 115 if (startsWithWordChar) 116 pattern.append("\\b"); 117 pattern.append(label); 118 if (endsWithWordChar) 119 pattern.append("\\b"); 120 } 121 pattern.append(")"); 122 result = new RegularExpression(pattern, TextCaseInsensitive); 123 } 124 125 // add regexp to the cache, making sure it is at the front for LRU ordering 126 if (cacheHit != 0) { 127 if (cacheHit != NSNotFound) { 128 // remove from old spot 129 [regExpLabels removeObjectAtIndex:cacheHit]; 130 regExps.remove(cacheHit); 131 } 132 // add to start 133 [regExpLabels insertObject:labels atIndex:0]; 134 regExps.insert(0, result); 135 // trim if too big 136 if ([regExpLabels count] > regExpCacheSize) { 137 [regExpLabels removeObjectAtIndex:regExpCacheSize]; 138 RegularExpression* last = regExps.last(); 139 regExps.removeLast(); 140 delete last; 141 } 142 } 143 return result; 144} 145 146NSString* Frame::searchForNSLabelsAboveCell(RegularExpression* regExp, HTMLTableCellElement* cell, size_t* resultDistanceFromStartOfCell) 147{ 148 RenderObject* cellRenderer = cell->renderer(); 149 150 if (cellRenderer && cellRenderer->isTableCell()) { 151 RenderTableCell* tableCellRenderer = toRenderTableCell(cellRenderer); 152 RenderTableCell* cellAboveRenderer = tableCellRenderer->table()->cellAbove(tableCellRenderer); 153 154 if (cellAboveRenderer) { 155 HTMLTableCellElement* aboveCell = 156 static_cast<HTMLTableCellElement*>(cellAboveRenderer->node()); 157 158 if (aboveCell) { 159 // search within the above cell we found for a match 160 size_t lengthSearched = 0; 161 for (Node* n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) { 162 if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { 163 // For each text chunk, run the regexp 164 String nodeString = n->nodeValue(); 165 int pos = regExp->searchRev(nodeString); 166 if (pos >= 0) { 167 if (resultDistanceFromStartOfCell) 168 *resultDistanceFromStartOfCell = lengthSearched; 169 return nodeString.substring(pos, regExp->matchedLength()); 170 } 171 lengthSearched += nodeString.length(); 172 } 173 } 174 } 175 } 176 } 177 // Any reason in practice to search all cells in that are above cell? 178 if (resultDistanceFromStartOfCell) 179 *resultDistanceFromStartOfCell = notFound; 180 return nil; 181} 182 183NSString* Frame::searchForLabelsBeforeElement(NSArray* labels, Element* element, size_t* resultDistance, bool* resultIsInCellAbove) 184{ 185 RegularExpression* regExp = regExpForLabels(labels); 186 // We stop searching after we've seen this many chars 187 const unsigned int charsSearchedThreshold = 500; 188 // This is the absolute max we search. We allow a little more slop than 189 // charsSearchedThreshold, to make it more likely that we'll search whole nodes. 190 const unsigned int maxCharsSearched = 600; 191 // If the starting element is within a table, the cell that contains it 192 HTMLTableCellElement* startingTableCell = 0; 193 bool searchedCellAbove = false; 194 195 if (resultDistance) 196 *resultDistance = notFound; 197 if (resultIsInCellAbove) 198 *resultIsInCellAbove = false; 199 200 // walk backwards in the node tree, until another element, or form, or end of tree 201 int unsigned lengthSearched = 0; 202 Node* n; 203 for (n = element->traversePreviousNode(); 204 n && lengthSearched < charsSearchedThreshold; 205 n = n->traversePreviousNode()) 206 { 207 if (n->hasTagName(formTag) 208 || (n->isHTMLElement() && static_cast<Element*>(n)->isFormControlElement())) 209 { 210 // We hit another form element or the start of the form - bail out 211 break; 212 } else if (n->hasTagName(tdTag) && !startingTableCell) { 213 startingTableCell = static_cast<HTMLTableCellElement*>(n); 214 } else if (n->hasTagName(trTag) && startingTableCell) { 215 NSString* result = searchForLabelsAboveCell(regExp, startingTableCell, resultDistance); 216 if (result && [result length] > 0) { 217 if (resultIsInCellAbove) 218 *resultIsInCellAbove = true; 219 return result; 220 } 221 searchedCellAbove = true; 222 } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { 223 // For each text chunk, run the regexp 224 String nodeString = n->nodeValue(); 225 // add 100 for slop, to make it more likely that we'll search whole nodes 226 if (lengthSearched + nodeString.length() > maxCharsSearched) 227 nodeString = nodeString.right(charsSearchedThreshold - lengthSearched); 228 int pos = regExp->searchRev(nodeString); 229 if (pos >= 0) { 230 if (resultDistance) 231 *resultDistance = lengthSearched; 232 return nodeString.substring(pos, regExp->matchedLength()); 233 } 234 lengthSearched += nodeString.length(); 235 } 236 } 237 238 // If we started in a cell, but bailed because we found the start of the form or the 239 // previous element, we still might need to search the row above us for a label. 240 if (startingTableCell && !searchedCellAbove) { 241 NSString* result = searchForLabelsAboveCell(regExp, startingTableCell, resultDistance); 242 if (result && [result length] > 0) { 243 if (resultIsInCellAbove) 244 *resultIsInCellAbove = true; 245 return result; 246 } 247 } 248 249 return nil; 250} 251 252static NSString *matchLabelsAgainstString(NSArray *labels, const String& stringToMatch) 253{ 254 if (stringToMatch.isEmpty()) 255 return nil; 256 257 String mutableStringToMatch = stringToMatch; 258 259 // Make numbers and _'s in field names behave like word boundaries, e.g., "address2" 260 replace(mutableStringToMatch, RegularExpression("\\d", TextCaseSensitive), " "); 261 mutableStringToMatch.replace('_', ' '); 262 263 RegularExpression* regExp = regExpForLabels(labels); 264 // Use the largest match we can find in the whole string 265 int pos; 266 int length; 267 int bestPos = -1; 268 int bestLength = -1; 269 int start = 0; 270 do { 271 pos = regExp->match(mutableStringToMatch, start); 272 if (pos != -1) { 273 length = regExp->matchedLength(); 274 if (length >= bestLength) { 275 bestPos = pos; 276 bestLength = length; 277 } 278 start = pos + 1; 279 } 280 } while (pos != -1); 281 282 if (bestPos != -1) 283 return mutableStringToMatch.substring(bestPos, bestLength); 284 return nil; 285} 286 287NSString* Frame::matchLabelsAgainstElement(NSArray* labels, Element* element) 288{ 289 // Match against the name element, then against the id element if no match is found for the name element. 290 // See 7538330 for one popular site that benefits from the id element check. 291 // FIXME: This code is mirrored in Frame.cpp. It would be nice to make the Mac code call the platform-agnostic 292 // code, which would require converting the NSArray of NSStrings to a Vector of Strings somewhere along the way. 293 String resultFromNameAttribute = matchLabelsAgainstString(labels, element->getAttribute(nameAttr)); 294 if (!resultFromNameAttribute.isEmpty()) 295 return resultFromNameAttribute; 296 297 return matchLabelsAgainstString(labels, element->getAttribute(idAttr)); 298} 299 300NSImage* Frame::imageFromRect(NSRect rect) const 301{ 302 NSView* view = m_view->documentView(); 303 if (!view) 304 return nil; 305 if (![view respondsToSelector:@selector(drawSingleRect:)]) 306 return nil; 307 308 PaintBehavior oldPaintBehavior = m_view->paintBehavior(); 309 m_view->setPaintBehavior(oldPaintBehavior | PaintBehaviorFlattenCompositingLayers); 310 311 BEGIN_BLOCK_OBJC_EXCEPTIONS; 312 313 NSRect bounds = [view bounds]; 314 315 // Round image rect size in window coordinate space to avoid pixel cracks at HiDPI (4622794) 316 rect = [view convertRect:rect toView:nil]; 317 rect.size.height = roundf(rect.size.height); 318 rect.size.width = roundf(rect.size.width); 319 rect = [view convertRect:rect fromView:nil]; 320 321 NSImage* resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease]; 322 323 if (rect.size.width != 0 && rect.size.height != 0) { 324 [resultImage setFlipped:YES]; 325 [resultImage lockFocus]; 326 CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; 327 CGContextSaveGState(context); 328 CGContextTranslateCTM(context, bounds.origin.x - rect.origin.x, bounds.origin.y - rect.origin.y); 329 330 // Note: Must not call drawRect: here, because drawRect: assumes that it's called from AppKit's 331 // display machinery. It calls getRectsBeingDrawn:count:, which can only be called inside 332 // when a real AppKit display is underway. 333 [view drawSingleRect:rect]; 334 335 CGContextRestoreGState(context); 336 [resultImage unlockFocus]; 337 [resultImage setFlipped:NO]; 338 } 339 340 m_view->setPaintBehavior(oldPaintBehavior); 341 return resultImage; 342 343 END_BLOCK_OBJC_EXCEPTIONS; 344 345 m_view->setPaintBehavior(oldPaintBehavior); 346 return nil; 347} 348 349NSImage* Frame::selectionImage(bool forceBlackText) const 350{ 351 m_view->setPaintBehavior(PaintBehaviorSelectionOnly | (forceBlackText ? PaintBehaviorForceBlackText : 0)); 352 m_doc->updateLayout(); 353 NSImage* result = imageFromRect(selectionBounds()); 354 m_view->setPaintBehavior(PaintBehaviorNormal); 355 return result; 356} 357 358NSImage* Frame::snapshotDragImage(Node* node, NSRect* imageRect, NSRect* elementRect) const 359{ 360 RenderObject* renderer = node->renderer(); 361 if (!renderer) 362 return nil; 363 364 renderer->updateDragState(true); // mark dragged nodes (so they pick up the right CSS) 365 m_doc->updateLayout(); // forces style recalc - needed since changing the drag state might 366 // imply new styles, plus JS could have changed other things 367 IntRect topLevelRect; 368 NSRect paintingRect = renderer->paintingRootRect(topLevelRect); 369 370 m_view->setNodeToDraw(node); // invoke special sub-tree drawing mode 371 NSImage* result = imageFromRect(paintingRect); 372 renderer->updateDragState(false); 373 m_doc->updateLayout(); 374 m_view->setNodeToDraw(0); 375 376 if (elementRect) 377 *elementRect = topLevelRect; 378 if (imageRect) 379 *imageRect = paintingRect; 380 return result; 381} 382 383NSImage* Frame::nodeImage(Node* node) const 384{ 385 RenderObject* renderer = node->renderer(); 386 if (!renderer) 387 return nil; 388 389 m_doc->updateLayout(); // forces style recalc 390 391 IntRect topLevelRect; 392 NSRect paintingRect = renderer->paintingRootRect(topLevelRect); 393 394 m_view->setNodeToDraw(node); // invoke special sub-tree drawing mode 395 NSImage* result = imageFromRect(paintingRect); 396 m_view->setNodeToDraw(0); 397 398 return result; 399} 400 401NSDictionary* Frame::fontAttributesForSelectionStart() const 402{ 403 Node* nodeToRemove; 404 RenderStyle* style = styleForSelectionStart(nodeToRemove); 405 if (!style) 406 return nil; 407 408 NSMutableDictionary* result = [NSMutableDictionary dictionary]; 409 410 if (style->backgroundColor().isValid() && style->backgroundColor().alpha() != 0) 411 [result setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName]; 412 413 if (style->font().primaryFont()->getNSFont()) 414 [result setObject:style->font().primaryFont()->getNSFont() forKey:NSFontAttributeName]; 415 416 if (style->color().isValid() && style->color() != Color::black) 417 [result setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName]; 418 419 ShadowData* shadow = style->textShadow(); 420 if (shadow) { 421 NSShadow* s = [[NSShadow alloc] init]; 422 [s setShadowOffset:NSMakeSize(shadow->x, shadow->y)]; 423 [s setShadowBlurRadius:shadow->blur]; 424 [s setShadowColor:nsColor(shadow->color)]; 425 [result setObject:s forKey:NSShadowAttributeName]; 426 } 427 428 int decoration = style->textDecorationsInEffect(); 429 if (decoration & LINE_THROUGH) 430 [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName]; 431 432 int superscriptInt = 0; 433 switch (style->verticalAlign()) { 434 case BASELINE: 435 case BOTTOM: 436 case BASELINE_MIDDLE: 437 case LENGTH: 438 case MIDDLE: 439 case TEXT_BOTTOM: 440 case TEXT_TOP: 441 case TOP: 442 break; 443 case SUB: 444 superscriptInt = -1; 445 break; 446 case SUPER: 447 superscriptInt = 1; 448 break; 449 } 450 if (superscriptInt) 451 [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName]; 452 453 if (decoration & UNDERLINE) 454 [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; 455 456 if (nodeToRemove) { 457 ExceptionCode ec = 0; 458 nodeToRemove->remove(ec); 459 ASSERT(ec == 0); 460 } 461 462 return result; 463} 464 465NSWritingDirection Frame::baseWritingDirectionForSelectionStart() const 466{ 467 NSWritingDirection result = NSWritingDirectionLeftToRight; 468 469 Position pos = selection()->selection().visibleStart().deepEquivalent(); 470 Node* node = pos.node(); 471 if (!node) 472 return result; 473 474 RenderObject* renderer = node->renderer(); 475 if (!renderer) 476 return result; 477 478 if (!renderer->isBlockFlow()) { 479 renderer = renderer->containingBlock(); 480 if (!renderer) 481 return result; 482 } 483 484 RenderStyle* style = renderer->style(); 485 if (!style) 486 return result; 487 488 switch (style->direction()) { 489 case LTR: 490 result = NSWritingDirectionLeftToRight; 491 break; 492 case RTL: 493 result = NSWritingDirectionRightToLeft; 494 break; 495 } 496 497 return result; 498} 499 500#if ENABLE(DASHBOARD_SUPPORT) 501NSMutableDictionary* Frame::dashboardRegionsDictionary() 502{ 503 Document* doc = document(); 504 505 const Vector<DashboardRegionValue>& regions = doc->dashboardRegions(); 506 size_t n = regions.size(); 507 508 // Convert the Vector<DashboardRegionValue> into a NSDictionary of WebDashboardRegions 509 NSMutableDictionary* webRegions = [NSMutableDictionary dictionaryWithCapacity:n]; 510 for (size_t i = 0; i < n; i++) { 511 const DashboardRegionValue& region = regions[i]; 512 513 if (region.type == StyleDashboardRegion::None) 514 continue; 515 516 NSString *label = region.label; 517 WebDashboardRegionType type = WebDashboardRegionTypeNone; 518 if (region.type == StyleDashboardRegion::Circle) 519 type = WebDashboardRegionTypeCircle; 520 else if (region.type == StyleDashboardRegion::Rectangle) 521 type = WebDashboardRegionTypeRectangle; 522 NSMutableArray *regionValues = [webRegions objectForKey:label]; 523 if (!regionValues) { 524 regionValues = [[NSMutableArray alloc] initWithCapacity:1]; 525 [webRegions setObject:regionValues forKey:label]; 526 [regionValues release]; 527 } 528 529 WebDashboardRegion *webRegion = [[WebDashboardRegion alloc] initWithRect:region.bounds clip:region.clip type:type]; 530 [regionValues addObject:webRegion]; 531 [webRegion release]; 532 } 533 534 return webRegions; 535} 536#endif 537 538DragImageRef Frame::dragImageForSelection() 539{ 540 if (!selection()->isRange()) 541 return nil; 542 return selectionImage(); 543} 544 545} // namespace WebCore 546