| /* |
| * 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 "EventNames.h" |
| #import "FrameLoaderClient.h" |
| #import "FramePrivate.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 "UserStyleSheetLoader.h" |
| #import "WebCoreViewFactory.h" |
| #import "visible_units.h" |
| |
| #import <Carbon/Carbon.h> |
| #import <kjs/JSLock.h> |
| |
| #if ENABLE(DASHBOARD_SUPPORT) |
| #import "WebDashboardRegion.h" |
| #endif |
| |
| @interface NSView (WebCoreHTMLDocumentView) |
| - (void)drawSingleRect:(NSRect)rect; |
| @end |
| |
| using namespace std; |
| |
| using JSC::JSLock; |
| |
| namespace WebCore { |
| |
| using namespace EventNames; |
| 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) |
| 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; |
| static Vector<RegularExpression*> regExps; |
| static RegularExpression wordRegExp = RegularExpression("\\w"); |
| |
| 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.search(label.substring(0, 1)) >= 0; |
| endsWithWordChar = wordRegExp.search(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, false); |
| } |
| |
| // 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) |
| { |
| RenderTableCell* cellRenderer = static_cast<RenderTableCell*>(cell->renderer()); |
| |
| if (cellRenderer && cellRenderer->isTableCell()) { |
| RenderTableCell* cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer); |
| |
| if (cellAboveRenderer) { |
| HTMLTableCellElement* aboveCell = |
| static_cast<HTMLTableCellElement*>(cellAboveRenderer->element()); |
| |
| if (aboveCell) { |
| // search within the above cell we found for a match |
| 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) |
| return nodeString.substring(pos, regExp->matchedLength()); |
| } |
| } |
| } |
| } |
| } |
| // Any reason in practice to search all cells in that are above cell? |
| return nil; |
| } |
| |
| NSString* Frame::searchForLabelsBeforeElement(NSArray* labels, Element* element) |
| { |
| 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; |
| |
| // 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<HTMLElement*>(n)->isGenericFormElement())) |
| { |
| // 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); |
| if (result && [result length] > 0) |
| 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) |
| 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); |
| if (result && [result length] > 0) |
| return result; |
| } |
| |
| return nil; |
| } |
| |
| NSString* Frame::matchLabelsAgainstElement(NSArray* labels, Element* element) |
| { |
| String name = element->getAttribute(nameAttr); |
| if (name.isEmpty()) |
| return nil; |
| |
| // Make numbers and _'s in field names behave like word boundaries, e.g., "address2" |
| replace(name, RegularExpression("\\d"), " "); |
| name.replace('_', ' '); |
| |
| RegularExpression* regExp = regExpForLabels(labels); |
| // Use the largest match we can find in the whole name string |
| int pos; |
| int length; |
| int bestPos = -1; |
| int bestLength = -1; |
| int start = 0; |
| do { |
| pos = regExp->search(name, start); |
| if (pos != -1) { |
| length = regExp->matchedLength(); |
| if (length >= bestLength) { |
| bestPos = pos; |
| bestLength = length; |
| } |
| start = pos + 1; |
| } |
| } while (pos != -1); |
| |
| if (bestPos != -1) |
| return name.substring(bestPos, bestLength); |
| return nil; |
| } |
| |
| NSImage* Frame::imageFromRect(NSRect rect) const |
| { |
| NSView* view = d->m_view->documentView(); |
| if (!view) |
| return nil; |
| if (![view respondsToSelector:@selector(drawSingleRect:)]) |
| return nil; |
| |
| NSImage* resultImage; |
| 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]; |
| |
| 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]; |
| } |
| |
| return resultImage; |
| |
| END_BLOCK_OBJC_EXCEPTIONS; |
| |
| return nil; |
| } |
| |
| NSImage* Frame::selectionImage(bool forceBlackText) const |
| { |
| d->m_view->setPaintRestriction(forceBlackText ? PaintRestrictionSelectionOnlyBlackText : PaintRestrictionSelectionOnly); |
| d->m_doc->updateLayout(); |
| NSImage* result = imageFromRect(selectionRect()); |
| d->m_view->setPaintRestriction(PaintRestrictionNone); |
| 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) |
| d->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); |
| |
| d->m_view->setNodeToDraw(node); // invoke special sub-tree drawing mode |
| NSImage* result = imageFromRect(paintingRect); |
| renderer->updateDragState(false); |
| d->m_doc->updateLayout(); |
| d->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; |
| |
| d->m_doc->updateLayout(); // forces style recalc |
| |
| IntRect topLevelRect; |
| NSRect paintingRect = renderer->paintingRootRect(topLevelRect); |
| |
| d->m_view->setNodeToDraw(node); // invoke special sub-tree drawing mode |
| NSImage* result = imageFromRect(paintingRect); |
| d->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->backgroundColor().isValid() && style->backgroundColor().alpha() != 0) |
| [result setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName]; |
| |
| if (style->font().primaryFont()->getNSFont()) |
| [result setObject:style->font().primaryFont()->getNSFont() forKey:NSFontAttributeName]; |
| |
| if (style->color().isValid() && style->color() != Color::black) |
| [result setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName]; |
| |
| 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; |
| } |
| |
| const short enableRomanKeyboardsOnly = -23; |
| void Frame::setUseSecureKeyboardEntry(bool enable) |
| { |
| if (enable == IsSecureEventInputEnabled()) |
| return; |
| if (enable) { |
| EnableSecureEventInput(); |
| #ifdef BUILDING_ON_TIGER |
| KeyScript(enableRomanKeyboardsOnly); |
| #else |
| CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); |
| TSMSetDocumentProperty(TSMGetActiveDocument(), kTSMDocumentEnabledInputSourcesPropertyTag, sizeof(CFArrayRef), &inputSources); |
| CFRelease(inputSources); |
| #endif |
| } else { |
| DisableSecureEventInput(); |
| #ifdef BUILDING_ON_TIGER |
| KeyScript(smKeyEnableKybds); |
| #else |
| TSMRemoveDocumentProperty(TSMGetActiveDocument(), kTSMDocumentEnabledInputSourcesPropertyTag); |
| #endif |
| } |
| } |
| |
| #if ENABLE(DASHBOARD_SUPPORT) |
| NSMutableDictionary* Frame::dashboardRegionsDictionary() |
| { |
| Document* doc = document(); |
| if (!doc) |
| return nil; |
| |
| 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(); |
| } |
| |
| void Frame::setUserStyleSheetLocation(const KURL& url) |
| { |
| delete d->m_userStyleSheetLoader; |
| d->m_userStyleSheetLoader = 0; |
| if (d->m_doc && d->m_doc->docLoader()) |
| d->m_userStyleSheetLoader = new UserStyleSheetLoader(d->m_doc, url.string()); |
| } |
| |
| void Frame::setUserStyleSheet(const String& styleSheet) |
| { |
| delete d->m_userStyleSheetLoader; |
| d->m_userStyleSheetLoader = 0; |
| if (d->m_doc) |
| d->m_doc->setUserStyleSheet(styleSheet); |
| } |
| |
| } // namespace WebCore |