• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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