FrameMac.mm   [plain text]


/*
 * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
 * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
 * Copyright (C) 2007 Trolltech ASA
 *
 * 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 "AXObjectCache.h"
#import "BeforeUnloadEvent.h"
#import "BlockExceptions.h"
#import "CSSHelper.h"
#import "Cache.h"
#import "Chrome.h"
#import "ColorMac.h"
#import "Cursor.h"
#import "DOMInternal.h"
#import "DocumentLoader.h"
#import "EditCommand.h"
#import "EditorClient.h"
#import "Event.h"
#import "EventListener.h"
#import "EventNames.h"
#import "FloatRect.h"
#import "FoundationExtras.h"
#import "FrameLoadRequest.h"
#import "FrameLoader.h"
#import "FrameLoaderClient.h"
#import "FrameLoaderTypes.h"
#import "FramePrivate.h"
#import "FrameView.h"
#import "GraphicsContext.h"
#import "HTMLDocument.h"
#import "HTMLFormElement.h"
#import "HTMLGenericFormElement.h"
#import "HTMLInputElement.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 "PlatformScrollBar.h"
#import "PlatformWheelEvent.h"
#import "Plugin.h"
#import "RegularExpression.h"
#import "RenderImage.h"
#import "RenderListItem.h"
#import "RenderPart.h"
#import "RenderTableCell.h"
#import "RenderTheme.h"
#import "RenderView.h"
#import "ResourceHandle.h"
#import "Settings.h"
#import "SimpleFontData.h"
#import "SystemTime.h"
#import "TextResourceDecoder.h"
#import "UserStyleSheetLoader.h"
#import "WebCoreFrameBridge.h"
#import "WebCoreSystemInterface.h"
#import "WebCoreViewFactory.h"
#import "WebScriptObjectPrivate.h"
#import "kjs_events.h"
#import "kjs_proxy.h"
#import "kjs_window.h"
#import "visible_units.h"
#import "Transform3D.h"
#import <GraphicsServices/GraphicsServices.h>
#import <wtf/GetPtr.h>

#if ENABLE(DASHBOARD_SUPPORT)
#import "WebDashboardRegion.h"
#endif

#undef _webcore_TIMING

#ifndef NSView
#define NSView WAKView
#endif

@interface NSObject (WebPlugIn)
- (id)objectForWebScript;
@end
 
@interface NSView (WebCoreHTMLDocumentView)
- (void)drawSingleRect:(NSRect)rect;
@end
 
using namespace std;
using namespace KJS::Bindings;

using KJS::JSLock;

namespace WebCore {

using namespace EventNames;
using namespace HTMLNames;

void Frame::setBridge(WebCoreFrameBridge* bridge)
{ 
    if (d->m_bridge == bridge)
        return;

    if (!bridge) {
        [d->m_bridge clearFrame];
        HardRelease(d->m_bridge);
        d->m_bridge = nil;
        return;
    }
    HardRetain(bridge);
    HardRelease(d->m_bridge);
    d->m_bridge = bridge;
}

WebCoreFrameBridge* Frame::bridge() const
{
    return d->m_bridge;
}

// 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 {
        DeprecatedString pattern("(");
        unsigned int numLabels = [labels count];
        unsigned int i;
        for (i = 0; i < numLabels; i++) {
            DeprecatedString label = DeprecatedString::fromNSString((NSString*)[labels objectAtIndex:i]);

            bool startsWithWordChar = false;
            bool endsWithWordChar = false;
            if (label.length() != 0) {
                startsWithWordChar = wordRegExp.search(label.at(0)) >= 0;
                endsWithWordChar = wordRegExp.search(label.at(label.length() - 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
                        DeprecatedString nodeString = n->nodeValue().deprecatedString();
                        int pos = regExp->searchRev(nodeString);
                        if (pos >= 0)
                            return nodeString.mid(pos, regExp->matchedLength()).getNSString();
                    }
                }
            }
        }
    }
    // 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
            DeprecatedString nodeString = n->nodeValue().deprecatedString();
            // 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.mid(pos, regExp->matchedLength()).getNSString();

            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)
{
    DeprecatedString name = element->getAttribute(nameAttr).deprecatedString();
    // Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
    name.replace(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.mid(bestPos, bestLength).getNSString();
    return nil;
}


NSDictionary* Frame::fontAttributesForSelectionStart() const
{
    Node* nodeToRemove;
    RenderStyle* style = styleForSelectionStart(nodeToRemove);
    if (!style)
        return nil;

    NSMutableDictionary* result = [NSMutableDictionary dictionary];


    return result;
}

NSWritingDirection Frame::baseWritingDirectionForSelectionStart() const
{
    NSWritingDirection result = NSWritingDirectionLeftToRight;

    Position pos = selectionController()->selection().visibleStart().deepEquivalent();
    Node* node = pos.node();
    if (!node || !node->renderer() || !node->renderer()->containingBlock())
        return result;
    RenderStyle* style = node->renderer()->containingBlock()->style();
    if (!style)
        return result;
        
    switch (style->direction()) {
        case LTR:
            result = NSWritingDirectionLeftToRight;
            break;
        case RTL:
            result = NSWritingDirectionRightToLeft;
            break;
    }

    return result;
}

void Frame::issuePasteCommand()
{
    [d->m_bridge issuePasteCommand];
}

const short enableRomanKeyboardsOnly = -23;
void Frame::setUseSecureKeyboardEntry(bool enable)
{
}

#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;
}

void Frame::dashboardRegionsChanged()
{
    NSMutableDictionary *webRegions = dashboardRegionsDictionary();
    [d->m_bridge dashboardRegionsChanged:webRegions];
}
#endif

void Frame::willPopupMenu(NSMenu * menu)
{
    [d->m_bridge willPopupMenu:menu];
}

FloatRect Frame::customHighlightLineRect(const AtomicString& type, const FloatRect& lineRect, Node* node)
{
    return [d->m_bridge customHighlightRect:type forLine:lineRect representedNode:node];
}

void Frame::paintCustomHighlight(const AtomicString& type, const FloatRect& boxRect, const FloatRect& lineRect, bool text, bool line, Node* node)
{
    [d->m_bridge paintCustomHighlight:type forBox:boxRect onLine:lineRect behindText:text entireLine:line representedNode:node];
}

DragImageRef Frame::dragImageForSelection() 
{
    return nil;
}


KJS::Bindings::Instance* Frame::createScriptInstanceForWidget(WebCore::Widget* widget)
{
    NSView* aView = widget->getView();
    if (!aView)
        return 0;

    void* nativeHandle = aView;
    CreateRootObjectFunction createRootObject = RootObject::createRootObject();
    RefPtr<RootObject> rootObject = createRootObject(nativeHandle);

    if ([aView respondsToSelector:@selector(objectForWebScript)]) {
        id objectForWebScript = [aView objectForWebScript];
        if (objectForWebScript)
            return Instance::createBindingForLanguageInstance(Instance::ObjectiveCLanguage, objectForWebScript, rootObject.release());
        return 0;
    }

    
    return 0;
}

WebScriptObject* Frame::windowScriptObject()
{
    if (!scriptProxy()->isEnabled())
        return 0;

    if (!d->m_windowScriptObject) {
        KJS::JSLock lock;
        KJS::JSObject* win = KJS::Window::retrieveWindow(this);
        KJS::Bindings::RootObject *root = bindingRootObject();
        d->m_windowScriptObject = [WebScriptObject scriptObjectForJSObject:toRef(win) originRootObject:root rootObject:root];
    }

    return d->m_windowScriptObject.get();
}

void Frame::clearPlatformScriptObjects()
{
    if (d->m_windowScriptObject) {
        KJS::Bindings::RootObject* root = bindingRootObject();
        [d->m_windowScriptObject.get() _setOriginRootObject:root andRootObject:root];
    }
}

#define NSFloatValue(aFloat) [NSNumber numberWithFloat:aFloat]

NSDictionary* Frame::dictionaryForViewportArguments(ViewportArguments arguments)
{
    return [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:NSFloatValue(arguments.initialScale), NSFloatValue(arguments.minimumScale), 
                                                NSFloatValue(arguments.maximumScale), NSFloatValue(arguments.userScalable), 
                                                NSFloatValue(arguments.width), NSFloatValue(arguments.height), nil] 
                                       forKeys:[NSArray arrayWithObjects:@"initial-scale", @"minimum-scale", @"maximum-scale", @"user-scalable", @"width", @"height", nil]];
}

ViewportArguments Frame::viewportArguments()
{
    return d->m_viewportArguments;
}

void Frame::setViewportArguments(ViewportArguments arguments)
{
    d->m_viewportArguments = arguments;
}

void Frame::didReceiveViewportArguments(ViewportArguments arguments)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    [bridge() didReceiveViewportArguments:dictionaryForViewportArguments(arguments)];
    END_BLOCK_OBJC_EXCEPTIONS;
}

void Frame::setNeedsScrollNotifications(bool aFlag)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    [bridge() setNeedsScrollNotifications:[NSNumber numberWithBool:aFlag]];
    END_BLOCK_OBJC_EXCEPTIONS;
}

void Frame::deferredContentChangeObserved()
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    [bridge() didObserveDeferredContentChange];
    END_BLOCK_OBJC_EXCEPTIONS;
}

void Frame::clearObservedContentModifiers()
{
    if (WebThreadCountOfObservedContentModifiers() > 0) {
        WebThreadClearObservedContentModifiers();
        deferredContentChangeObserved();
    }        
}

void Frame::didPreventDefaultForEvent(GSEventRef event)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    [bridge() didPreventDefaultForEvent:event];
    END_BLOCK_OBJC_EXCEPTIONS;
}

#if ENABLE(IPHONE_PPT)
static int sAllLayouts = 0;

void Frame::didParse(double duration)
{
    d->m_parseCount++;
    d->m_parseDuration += duration;
}

void Frame::didLayout(bool firstLayout, double duration)
{
    sAllLayouts++;
    d->m_layoutCount++;
    d->m_layoutDuration += duration;
}

void Frame::didForcedLayout()
{
    d->m_forcedLayoutCount++;
}

void Frame::getPPTStats(unsigned& parseCount, unsigned& layoutCount, unsigned& forcedLayoutCount, CFTimeInterval& parseDuration, CFTimeInterval& layoutDuration)
{
    parseCount = d->m_parseCount;
    layoutCount = d->m_layoutCount;
    forcedLayoutCount = d->m_forcedLayoutCount;
    parseDuration = d->m_parseDuration;
    layoutDuration = d->m_layoutDuration;

//fprintf(stderr, "All Layouts: %d\n", sAllLayouts);    
}

void Frame::clearPPTStats()
{
    d->m_parseCount = 0;
    d->m_layoutCount = 0;
    d->m_forcedLayoutCount = 0;
    d->m_parseDuration = 0.0;
    d->m_layoutDuration = 0.0;
}
#endif

int Frame::maximumImageSize()
{
    return [bridge() getMaximumImageSize];
}

void Frame::notifySelectionLayoutChanged() const
{
    [bridge() caretChanged];
}

int Frame::orientation() const
{
    return [bridge() rotationDegrees];
}
    
void Frame::didReceiveDocType()
{
    [bridge() didReceiveDocType];
}

char *Frame::windowState()
{
    return [bridge() windowState];
}
    

#if ENABLE(HW_COMP)
void* Frame::contentsHostingLayer() const
{
    return (void*)[bridge() contentsHostingLayer];
}
#endif  // ENABLE(HW_COMP) && PLATFORM(IPHONE)

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, bool saveStyleSheet)
{
    if (saveStyleSheet)
        d->m_userStyleSheet = styleSheet;

    delete d->m_userStyleSheetLoader;
    d->m_userStyleSheetLoader = 0;
    if (d->m_doc)
        d->m_doc->setUserStyleSheet(styleSheet);
}

} // namespace WebCore