Home | History | Annotate | Download | only in mac
      1 /*
      2  * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
      3  * Copyright (C) 2006 Alexey Proskuryakov (ap (at) 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 
     69 using namespace std;
     70 
     71 namespace WebCore {
     72 
     73 using 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)
     77 static 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 
    146 NSString* 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 
    183 NSString* 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 
    252 static 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 
    287 NSString* 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 
    300 NSImage* 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 
    349 NSImage* 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 
    358 NSImage* 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 
    383 NSImage* 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 
    401 NSDictionary* 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 
    465 NSWritingDirection 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)
    501 NSMutableDictionary* 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 
    538 DragImageRef Frame::dragImageForSelection()
    539 {
    540     if (!selection()->isRange())
    541         return nil;
    542     return selectionImage();
    543 }
    544 
    545 } // namespace WebCore
    546