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 "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 
     62 using namespace std;
     63 
     64 namespace WebCore {
     65 
     66 using 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)
     70 static 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 
    139 NSString* 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 
    208 static 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 
    243 NSString* 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 
    256 NSImage* 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 
    288 NSImage* 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 
    297 NSImage* 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 
    322 DragImageRef 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 
    340 DragImageRef Frame::dragImageForSelection()
    341 {
    342     if (!selection()->isRange())
    343         return nil;
    344     return selectionImage();
    345 }
    346 
    347 } // namespace WebCore
    348