WebCore/page/mac/FrameMac.mm
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/page/mac/FrameMac.mm	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,545 @@
+/*
+ * 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 <Carbon/Carbon.h>
+#import <wtf/StdLibExtras.h>
+
+#if ENABLE(DASHBOARD_SUPPORT)
+#import "WebDashboardRegion.h"
+#endif
+ 
+@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));
+}
+
+NSImage* Frame::imageFromRect(NSRect rect) const
+{
+    NSView* view = m_view->documentView();
+    if (!view)
+        return nil;
+    if (![view respondsToSelector:@selector(drawSingleRect:)])
+        return nil;
+    
+    PaintBehavior oldPaintBehavior = m_view->paintBehavior();
+    m_view->setPaintBehavior(oldPaintBehavior | PaintBehaviorFlattenCompositingLayers);
+
+    BEGIN_BLOCK_OBJC_EXCEPTIONS;
+    
+    NSRect bounds = [view bounds];
+    
+    // Round image rect size in window coordinate space to avoid pixel cracks at HiDPI (4622794)
+    rect = [view convertRect:rect toView:nil];
+    rect.size.height = roundf(rect.size.height);
+    rect.size.width = roundf(rect.size.width);
+    rect = [view convertRect:rect fromView:nil];
+    
+    NSImage* resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease];
+
+    if (rect.size.width != 0 && rect.size.height != 0) {
+        [resultImage setFlipped:YES];
+        [resultImage lockFocus];
+        CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+        CGContextSaveGState(context);
+        CGContextTranslateCTM(context, bounds.origin.x - rect.origin.x, bounds.origin.y - rect.origin.y);
+
+        // Note: Must not call drawRect: here, because drawRect: assumes that it's called from AppKit's
+        // display machinery. It calls getRectsBeingDrawn:count:, which can only be called inside
+        // when a real AppKit display is underway.
+        [view drawSingleRect:rect];
+
+        CGContextRestoreGState(context);
+        [resultImage unlockFocus];
+        [resultImage setFlipped:NO];
+    }
+
+    m_view->setPaintBehavior(oldPaintBehavior);
+    return resultImage;
+
+    END_BLOCK_OBJC_EXCEPTIONS;
+    
+    m_view->setPaintBehavior(oldPaintBehavior);
+    return nil;
+}
+
+NSImage* Frame::selectionImage(bool forceBlackText) const
+{
+    m_view->setPaintBehavior(PaintBehaviorSelectionOnly | (forceBlackText ? PaintBehaviorForceBlackText : 0));
+    m_doc->updateLayout();
+    NSImage* result = imageFromRect(selectionBounds());
+    m_view->setPaintBehavior(PaintBehaviorNormal);
+    return result;
+}
+
+NSImage* Frame::snapshotDragImage(Node* node, NSRect* imageRect, NSRect* elementRect) const
+{
+    RenderObject* renderer = node->renderer();
+    if (!renderer)
+        return nil;
+    
+    renderer->updateDragState(true);    // mark dragged nodes (so they pick up the right CSS)
+    m_doc->updateLayout();        // forces style recalc - needed since changing the drag state might
+                                        // imply new styles, plus JS could have changed other things
+    IntRect topLevelRect;
+    NSRect paintingRect = renderer->paintingRootRect(topLevelRect);
+
+    m_view->setNodeToDraw(node);              // invoke special sub-tree drawing mode
+    NSImage* result = imageFromRect(paintingRect);
+    renderer->updateDragState(false);
+    m_doc->updateLayout();
+    m_view->setNodeToDraw(0);
+
+    if (elementRect)
+        *elementRect = topLevelRect;
+    if (imageRect)
+        *imageRect = paintingRect;
+    return result;
+}
+
+NSImage* Frame::nodeImage(Node* node) const
+{
+    RenderObject* renderer = node->renderer();
+    if (!renderer)
+        return nil;
+
+    m_doc->updateLayout(); // forces style recalc
+
+    IntRect topLevelRect;
+    NSRect paintingRect = renderer->paintingRootRect(topLevelRect);
+
+    m_view->setNodeToDraw(node); // invoke special sub-tree drawing mode
+    NSImage* result = imageFromRect(paintingRect);
+    m_view->setNodeToDraw(0);
+
+    return result;
+}
+
+NSDictionary* Frame::fontAttributesForSelectionStart() const
+{
+    Node* nodeToRemove;
+    RenderStyle* style = styleForSelectionStart(nodeToRemove);
+    if (!style)
+        return nil;
+
+    NSMutableDictionary* result = [NSMutableDictionary dictionary];
+
+    if (style->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && style->visitedDependentColor(CSSPropertyBackgroundColor).alpha() != 0)
+        [result setObject:nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName];
+
+    if (style->font().primaryFont()->getNSFont())
+        [result setObject:style->font().primaryFont()->getNSFont() forKey:NSFontAttributeName];
+
+    if (style->visitedDependentColor(CSSPropertyColor).isValid() && style->visitedDependentColor(CSSPropertyColor) != Color::black)
+        [result setObject:nsColor(style->visitedDependentColor(CSSPropertyColor)) forKey:NSForegroundColorAttributeName];
+
+    const ShadowData* shadow = style->textShadow();
+    if (shadow) {
+        NSShadow* s = [[NSShadow alloc] init];
+        [s setShadowOffset:NSMakeSize(shadow->x(), shadow->y())];
+        [s setShadowBlurRadius:shadow->blur()];
+        [s setShadowColor:nsColor(shadow->color())];
+        [result setObject:s forKey:NSShadowAttributeName];
+    }
+
+    int decoration = style->textDecorationsInEffect();
+    if (decoration & LINE_THROUGH)
+        [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
+
+    int superscriptInt = 0;
+    switch (style->verticalAlign()) {
+        case BASELINE:
+        case BOTTOM:
+        case BASELINE_MIDDLE:
+        case LENGTH:
+        case MIDDLE:
+        case TEXT_BOTTOM:
+        case TEXT_TOP:
+        case TOP:
+            break;
+        case SUB:
+            superscriptInt = -1;
+            break;
+        case SUPER:
+            superscriptInt = 1;
+            break;
+    }
+    if (superscriptInt)
+        [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName];
+
+    if (decoration & UNDERLINE)
+        [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
+
+    if (nodeToRemove) {
+        ExceptionCode ec = 0;
+        nodeToRemove->remove(ec);
+        ASSERT(ec == 0);
+    }
+
+    return result;
+}
+
+NSWritingDirection Frame::baseWritingDirectionForSelectionStart() const
+{
+    NSWritingDirection result = NSWritingDirectionLeftToRight;
+
+    Position pos = selection()->selection().visibleStart().deepEquivalent();
+    Node* node = pos.node();
+    if (!node)
+        return result;
+
+    RenderObject* renderer = node->renderer();
+    if (!renderer)
+        return result;
+
+    if (!renderer->isBlockFlow()) {
+        renderer = renderer->containingBlock();
+        if (!renderer)
+            return result;
+    }
+
+    RenderStyle* style = renderer->style();
+    if (!style)
+        return result;
+        
+    switch (style->direction()) {
+        case LTR:
+            result = NSWritingDirectionLeftToRight;
+            break;
+        case RTL:
+            result = NSWritingDirectionRightToLeft;
+            break;
+    }
+
+    return result;
+}
+
+#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() 
+{
+    if (!selection()->isRange())
+        return nil;
+    return selectionImage();
+}
+
+} // namespace WebCore