/*
 * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved.
 *           (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
 *
 * 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. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 OR ITS 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 "WebHTMLView.h"

#import "DOMNodeInternal.h"
#import "DOMRangeInternal.h"
#import "WebArchive.h"
#import "WebArchiver.h"
#import "WebBaseNetscapePluginViewInternal.h"
#import "WebClipView.h"
#import "WebDOMOperationsPrivate.h"
#import "WebDataSourceInternal.h"
#import "WebDefaultUIDelegate.h"
#import "WebDocumentInternal.h"
#import "WebEditingDelegate.h"
#import "WebElementDictionary.h"
#import "WebFrameBridge.h"
#import "WebFrameInternal.h"
#import "WebFramePrivate.h"
#import "WebFrameViewInternal.h"
#import "WebHTMLRepresentationPrivate.h"
#import "WebHTMLViewInternal.h"
#import "WebKitLogging.h"
#import "WebKitNSStringExtras.h"
#import "WebKitPluginContainerView.h"
#import "WebKitVersionChecks.h"
#import "WebLocalizableStrings.h"
#import "WebNSAttributedStringExtras.h"
#import "WebNSEventExtras.h"
#import "WebNSFileManagerExtras.h"
#import "WebNSImageExtras.h"
#import "WebNSObjectExtras.h"
#import "WebNSPasteboardExtras.h"
#import "WebNSPrintOperationExtras.h"
#import "WebNSURLExtras.h"
#import "WebNSViewExtras.h"
#import "WebNetscapePluginEmbeddedView.h"
#import "WebPluginController.h"
#import "WebPreferences.h"
#import "WebPreferencesPrivate.h"
#import "WebResourcePrivate.h"
#import "WebStringTruncator.h"
#import "WebUIDelegatePrivate.h"
#import "WebViewInternal.h"
#import <AppKit/NSAccessibility.h>
#import <ApplicationServices/ApplicationServices.h>
#import <dlfcn.h>
#import <WebCore/CachedImage.h>
#import <WebCore/CachedResourceClient.h>
#import <WebCore/ContextMenu.h>
#import <WebCore/ContextMenuController.h>
#import <WebCore/Document.h>
#import <WebCore/Editor.h>
#import <WebCore/EditorDeleteAction.h>
#import <WebCore/Element.h>
#import <WebCore/EventHandler.h>
#import <WebCore/EventNames.h>
#import <WebCore/ExceptionHandlers.h>
#import <WebCore/DragController.h>
#import <WebCore/FloatRect.h>
#import <WebCore/FocusController.h>
#import <WebCore/Frame.h>
#import <WebCore/FrameLoader.h>
#import <WebCore/FrameView.h>
#import <WebCore/HitTestResult.h>
#import <WebCore/HTMLNames.h>
#import <WebCore/Image.h>
#import <WebCore/KeyboardEvent.h>
#import <WebCore/MIMETypeRegistry.h>
#import <WebCore/Page.h>
#import <WebCore/PlatformKeyboardEvent.h>
#import <WebCore/PlatformMouseEvent.h>
#import <WebCore/Range.h>
#import <WebCore/SelectionController.h>
#import <WebCore/SharedBuffer.h>
#import <WebCore/WebCoreObjCExtras.h>
#import <WebCore/WebCoreTextRenderer.h>
#import <WebKit/DOM.h>
#import <WebKit/DOMExtensions.h>
#import <WebKit/DOMPrivate.h>
#import <WebKitSystemInterface.h>

using namespace WebCore;
using namespace HTMLNames;

extern "C" {

// Need to declare these attribute names because AppKit exports them but does not make them available in API or SPI headers.

extern NSString *NSMarkedClauseSegmentAttributeName; 
extern NSString *NSTextInputReplacementRangeAttributeName; 

// Kill ring calls. Would be better to use NSKillRing.h, but that's not available as API or SPI.

void _NSInitializeKillRing(void);
void _NSAppendToKillRing(NSString *);
void _NSPrependToKillRing(NSString *);
NSString *_NSYankFromKillRing(void);
NSString *_NSYankPreviousFromKillRing(void);
void _NSNewKillRingSequence(void);
void _NSSetKillRingToYankedState(void);
void _NSResetKillRingOperationFlag(void);

}

@interface NSView (AppKitSecretsIKnowAbout)
- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
- (NSRect)_dirtyRect;
- (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants;
- (void)_propagateDirtyRectsToOpaqueAncestors;
- (void)_windowChangedKeyState;
@end

@interface NSApplication (AppKitSecretsIKnowAbout)
- (void)speakString:(NSString *)string;
@end

@interface NSWindow (AppKitSecretsIKnowAbout)
- (id)_newFirstResponderAfterResigning;
- (void)_setForceActiveControls:(BOOL)flag;
@end

@interface NSAttributedString (AppKitSecretsIKnowAbout)
- (id)_initWithDOMRange:(DOMRange *)domRange;
- (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
@end

@interface NSSpellChecker (CurrentlyPrivateForTextView)
- (void)learnWord:(NSString *)word;
@end

// By imaging to a width a little wider than the available pixels,
// thin pages will be scaled down a little, matching the way they
// print in IE and Camino. This lets them use fewer sheets than they
// would otherwise, which is presumably why other browsers do this.
// Wide pages will be scaled down more than this.
#define PrintingMinimumShrinkFactor     1.25f

// This number determines how small we are willing to reduce the page content
// in order to accommodate the widest line. If the page would have to be
// reduced smaller to make the widest line fit, we just clip instead (this
// behavior matches MacIE and Mozilla, at least)
#define PrintingMaximumShrinkFactor     2.0f

// This number determines how short the last printed page of a multi-page print session
// can be before we try to shrink the scale in order to reduce the number of pages, and
// thus eliminate the orphan.
#define LastPrintedPageOrphanRatio      0.1f

// This number determines the amount the scale factor is adjusted to try to eliminate orphans.
// It has no direct mathematical relationship to LastPrintedPageOrphanRatio, due to variable
// numbers of pages, logic to avoid breaking elements, and CSS-supplied hard page breaks.
#define PrintingOrphanShrinkAdjustment  1.1f

#define AUTOSCROLL_INTERVAL             0.1f

#define DRAG_LABEL_BORDER_X             4.0f
//Keep border_y in synch with DragController::LinkDragBorderInset
#define DRAG_LABEL_BORDER_Y             2.0f
#define DRAG_LABEL_RADIUS               5.0f
#define DRAG_LABEL_BORDER_Y_OFFSET              2.0f

#define MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP        120.0f
#define MAX_DRAG_LABEL_WIDTH                    320.0f

#define DRAG_LINK_LABEL_FONT_SIZE   11.0f
#define DRAG_LINK_URL_FONT_SIZE   10.0f

// Any non-zero value will do, but using something recognizable might help us debug some day.
#define TRACKING_RECT_TAG 0xBADFACE

// FIXME: This constant is copied from AppKit's _NXSmartPaste constant.
#define WebSmartPastePboardType @"NeXT smart paste pasteboard type"

#define STANDARD_WEIGHT 5
#define MIN_BOLD_WEIGHT 9
#define STANDARD_BOLD_WEIGHT 10

// Fake URL scheme.
#define WebDataProtocolScheme @"webkit-fake-url"

// <rdar://problem/4985524> References to WebCoreScrollView as a subview of a WebHTMLView may be present
// in some NIB files, so NSUnarchiver must be still able to look up this now-unused class.
@interface WebCoreScrollView : NSScrollView
@end

@implementation WebCoreScrollView
@end

// if YES, do the standard NSView hit test (which can't give the right result when HTML overlaps a view)
static BOOL forceNSViewHitTest;

// if YES, do the "top WebHTMLView" hit test (which we'd like to do all the time but can't because of Java requirements [see bug 4349721])
static BOOL forceWebHTMLViewHitTest;

static WebHTMLView *lastHitView;

// We need this to be able to safely reference the CachedImage for the promised drag data
static CachedResourceClient* promisedDataClient()
{
    static CachedResourceClient* staticCachedResourceClient = new CachedResourceClient;
    return staticCachedResourceClient;
}

@interface WebHTMLView (WebTextSizing) <_WebDocumentTextSizing>
@end

@interface WebHTMLView (WebHTMLViewFileInternal)
- (BOOL)_imageExistsAtPaths:(NSArray *)paths;
- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText chosePlainText:(BOOL *)chosePlainText;
- (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard;
- (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
- (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard;
- (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
- (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
- (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action;
- (float)_calculatePrintHeight;
- (void)_updateTextSizeMultiplier;
- (DOMRange *)_selectedRange;
- (BOOL)_shouldDeleteRange:(DOMRange *)range;
- (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard;
- (NSView *)_hitViewForEvent:(NSEvent *)event;
- (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString;
- (DOMRange *)_documentRange;
- (WebFrameBridge *)_bridge;
- (void)_setMouseDownEvent:(NSEvent *)event;
- (WebHTMLView *)_topHTMLView;
- (BOOL)_isTopHTMLView;
@end

@interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in a normal category and stop doing the forward declaration trick.
- (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize;
@end

@interface WebHTMLView (WebNSTextInputSupport) <NSTextInput>
- (void)_updateSelectionForInputManager;
@end

@interface WebHTMLView (WebEditingStyleSupport)
- (DOMCSSStyleDeclaration *)_emptyStyle;
- (NSString *)_colorAsString:(NSColor *)color;
@end

@interface NSView (WebHTMLViewFileInternal)
- (void)_web_setPrintingModeRecursive;
- (void)_web_setPrintingModeRecursiveAndAdjustViewSize;
- (void)_web_clearPrintingModeRecursive;
- (void)_web_layoutIfNeededRecursive;
@end

@interface NSMutableDictionary (WebHTMLViewFileInternal)
- (void)_web_setObjectIfNotNil:(id)object forKey:(id)key;
@end

// Handles the complete: text command
@interface WebTextCompleteController : NSObject {
@private
    WebHTMLView *_view;
    NSWindow *_popupWindow;
    NSTableView *_tableView;
    NSArray *_completions;
    NSString *_originalString;
    int prefixLength;
}
- (id)initWithHTMLView:(WebHTMLView *)view;
- (void)doCompletion;
- (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft;
- (BOOL)popupWindowIsOpen;
- (BOOL)filterKeyDown:(NSEvent *)event;
- (void)_reflectSelection;
@end

struct WebHTMLViewInterpretKeyEventsParameters {
    KeyboardEvent* event;
    BOOL eventWasHandled;
    BOOL shouldSaveCommand;
    // The Input Method may consume an event and not tell us, in
    // which case we should not bubble the event up the DOM
    BOOL consumedByIM;
};

@implementation WebHTMLViewPrivate

#ifndef BUILDING_ON_TIGER
+ (void)initialize
{
    WebCoreObjCFinalizeOnMainThread(self);
}
#endif

- (void)dealloc
{
    ASSERT(!autoscrollTimer);
    ASSERT(!autoscrollTriggerEvent);
    ASSERT(!updateActiveStateTimer);
    ASSERT(!updateMouseoverTimer);
    
    [mouseDownEvent release];
    [keyDownEvent release];
    [pluginController release];
    [toolTip release];
    [compController release];
    [firstResponderTextViewAtMouseDownTime release];
    [dataSource release];
    [highlighters release];
    if (promisedDragTIFFDataSource)
        promisedDragTIFFDataSource->deref(promisedDataClient());

    [super dealloc];
}

- (void)finalize
{
    ASSERT_MAIN_THREAD();

    if (promisedDragTIFFDataSource)
        promisedDragTIFFDataSource->deref(promisedDataClient());

    [super finalize];
}

- (void)clear
{
    [mouseDownEvent release];
    [keyDownEvent release];
    [pluginController release];
    [toolTip release];
    [compController release];
    [firstResponderTextViewAtMouseDownTime release];
    [dataSource release];
    [highlighters release];
    if (promisedDragTIFFDataSource)
        promisedDragTIFFDataSource->deref(promisedDataClient());

    mouseDownEvent = nil;
    keyDownEvent = nil;
    pluginController = nil;
    toolTip = nil;
    compController = nil;
    firstResponderTextViewAtMouseDownTime = nil;
    dataSource = nil;
    highlighters = nil;
    promisedDragTIFFDataSource = 0;
}

@end

@implementation WebHTMLView (WebHTMLViewFileInternal)

- (DOMRange *)_documentRange
{
    return [[[self _frame] DOMDocument] _documentRange];
}

- (BOOL)_imageExistsAtPaths:(NSArray *)paths
{
    NSEnumerator *enumerator = [paths objectEnumerator];
    NSString *path;
    
    while ((path = [enumerator nextObject]) != nil) {
        NSString *MIMEType = WKGetMIMETypeForExtension([path pathExtension]);
        if (MIMETypeRegistry::isSupportedImageResourceMIMEType(MIMEType))
            return YES;
    }
    
    return NO;
}

- (WebDataSource *)_dataSource
{
    return _private->dataSource;
}

- (WebFrameBridge *)_bridge
{
    return [_private->dataSource _bridge];
}

- (WebView *)_webView
{
    return [_private->dataSource _webView];
}

- (WebFrameView *)_frameView
{
    return [[_private->dataSource webFrame] frameView];
}

- (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths
{
    DOMDocumentFragment *fragment;
    NSEnumerator *enumerator = [paths objectEnumerator];
    NSMutableArray *domNodes = [[NSMutableArray alloc] init];
    NSString *path;
    
    while ((path = [enumerator nextObject]) != nil) {
        // Non-image file types; _web_userVisibleString is appropriate here because this will
        // be pasted as visible text.
        NSString *url = [[[NSURL fileURLWithPath:path] _webkit_canonicalize] _web_userVisibleString];
        [domNodes addObject:[[[self _frame] DOMDocument] createTextNode: url]];
    }
    
    fragment = [[self _bridge] documentFragmentWithNodesAsParagraphs:domNodes]; 
    
    [domNodes release];
    
    return [fragment firstChild] != nil ? fragment : nil;
}

+ (NSArray *)_excludedElementsForAttributedStringConversion
{
    static NSArray *elements = nil;
    if (elements == nil) {
        elements = [[NSArray alloc] initWithObjects:
            // Omit style since we want style to be inline so the fragment can be easily inserted.
            @"style",
            // Omit xml so the result is not XHTML.
            @"xml", 
            // Omit tags that will get stripped when converted to a fragment anyway.
            @"doctype", @"html", @"head", @"body",
            // Omit deprecated tags.
            @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u",
            // Omit object so no file attachments are part of the fragment.
            @"object", nil];
        CFRetain(elements);
    }
    return elements;
}

static NSURL* uniqueURLWithRelativePart(NSString *relativePart)
{
    CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault);
    NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef);
    CFRelease(UUIDRef);
    NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/%@", WebDataProtocolScheme, UUIDString, relativePart]];
    CFRelease(UUIDString);

    return URL;
}

- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard
                                               inContext:(DOMRange *)context
                                          allowPlainText:(BOOL)allowPlainText
                                          chosePlainText:(BOOL *)chosePlainText
{
    NSArray *types = [pasteboard types];
    *chosePlainText = NO;
    DOMDocumentFragment *fragment = nil;

    if ([types containsObject:WebArchivePboardType] &&
        (fragment = [self _documentFragmentFromPasteboard:pasteboard 
                                                  forType:WebArchivePboardType
                                                inContext:context
                                             subresources:0]))
        return fragment;
                                           
    if ([types containsObject:NSFilenamesPboardType] &&
        (fragment = [self _documentFragmentFromPasteboard:pasteboard 
                                                  forType:NSFilenamesPboardType
                                                inContext:context
                                             subresources:0]))
        return fragment;
    
    if ([types containsObject:NSHTMLPboardType] &&
        (fragment = [self _documentFragmentFromPasteboard:pasteboard 
                                                  forType:NSHTMLPboardType
                                                inContext:context
                                             subresources:0]))
        return fragment;
    
    if ([types containsObject:NSRTFPboardType] &&
        (fragment = [self _documentFragmentFromPasteboard:pasteboard 
                                                  forType:NSRTFPboardType
                                                inContext:context
                                             subresources:0]))
        return fragment;

    if ([types containsObject:NSRTFDPboardType] &&
        (fragment = [self _documentFragmentFromPasteboard:pasteboard 
                                                  forType:NSRTFDPboardType
                                                inContext:context
                                             subresources:0]))
        return fragment;

    if ([types containsObject:NSTIFFPboardType] &&
        (fragment = [self _documentFragmentFromPasteboard:pasteboard 
                                                  forType:NSTIFFPboardType
                                                inContext:context
                                             subresources:0]))
        return fragment;

    if ([types containsObject:NSPICTPboardType] &&
        (fragment = [self _documentFragmentFromPasteboard:pasteboard 
                                                  forType:NSPICTPboardType
                                                inContext:context
                                             subresources:0]))
        return fragment;
    
    if ([types containsObject:NSURLPboardType] &&
        (fragment = [self _documentFragmentFromPasteboard:pasteboard 
                                                  forType:NSURLPboardType
                                                inContext:context
                                             subresources:0]))
        return fragment;
        
    if (allowPlainText && [types containsObject:NSStringPboardType] &&
        (fragment = [self _documentFragmentFromPasteboard:pasteboard
                                                  forType:NSStringPboardType
                                                inContext:context
                                             subresources:0])) {
        *chosePlainText = YES;
        return fragment;
    }
    
    return nil;
}

- (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard
{
    NSArray *types = [pasteboard types];
    
    if ([types containsObject:NSStringPboardType])
        return [pasteboard stringForType:NSStringPboardType];
    
    NSAttributedString *attributedString = nil;
    NSString *string;

    if ([types containsObject:NSRTFDPboardType])
        attributedString = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
    if (attributedString == nil && [types containsObject:NSRTFPboardType])
        attributedString = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
    if (attributedString != nil) {
        string = [[attributedString string] copy];
        [attributedString release];
        return [string autorelease];
    }
    
    if ([types containsObject:NSFilenamesPboardType]) {
        string = [[pasteboard propertyListForType:NSFilenamesPboardType] componentsJoinedByString:@"\n"];
        if (string != nil)
            return string;
    }
    
    NSURL *URL;
    
    if ((URL = [NSURL URLFromPasteboard:pasteboard])) {
        string = [URL _web_userVisibleString];
        if ([string length] > 0)
            return string;
    }
    
    return nil;
}

- (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText
{
    DOMRange *range = [self _selectedRange];
    BOOL chosePlainText;
    DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard
        inContext:range allowPlainText:allowPlainText chosePlainText:&chosePlainText];
    WebFrameBridge *bridge = [self _bridge];
    if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[self _selectedRange] givenAction:WebViewInsertActionPasted]) {
        [bridge replaceSelectionWithFragment:fragment selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard] matchStyle:chosePlainText];
    }
}

- (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard
{
    NSString *text = [self _plainTextFromPasteboard:pasteboard];
    if ([self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionPasted])
        [[self _bridge] replaceSelectionWithText:text selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]];
}

- (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
{
    WebView *webView = [self _webView];
    DOMNode *child = [fragment firstChild];
    if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]])
        return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action];
    return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action];
}

- (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
{
    WebView *webView = [self _webView];
    return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:range givenAction:action];
}

- (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action
{
    return [self _shouldInsertText:text replacingDOMRange:[self _selectedRange] givenAction:action];
}

// Calculate the vertical size of the view that fits on a single page
- (float)_calculatePrintHeight
{
    // Obtain the print info object for the current operation
    NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo];
    
    // Calculate the page height in points
    NSSize paperSize = [pi paperSize];
    return paperSize.height - [pi topMargin] - [pi bottomMargin];
}

- (void)_updateTextSizeMultiplier
{
    [[self _bridge] setTextSizeMultiplier:[[self _webView] textSizeMultiplier]];    
}

- (DOMRange *)_selectedRange
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame ? kit(coreFrame->selectionController()->toRange().get()) : nil;
}

- (BOOL)_shouldDeleteRange:(DOMRange *)range
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame && coreFrame->editor()->shouldDeleteRange(core(range));
}

- (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard
{
    return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType];
}

- (NSView *)_hitViewForEvent:(NSEvent *)event
{
    // Usually, we hack AK's hitTest method to catch all events at the topmost WebHTMLView.  
    // Callers of this method, however, want to query the deepest view instead.
    forceNSViewHitTest = YES;
    NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]];
    forceNSViewHitTest = NO;    
    return hitView;
}

- (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString
{
    // Put HTML on the pasteboard.
    if ([types containsObject:WebArchivePboardType]) {
        WebArchive *archive = [WebArchiver archiveSelectionInFrame:[self _frame]];
        [pasteboard setData:[archive data] forType:WebArchivePboardType];
    }
    
    // Put the attributed string on the pasteboard (RTF/RTFD format).
    if ([types containsObject:NSRTFDPboardType]) {
        if (attributedString == nil) {
            attributedString = [self selectedAttributedString];
        }        
        NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
        [pasteboard setData:RTFDData forType:NSRTFDPboardType];
    }        
    if ([types containsObject:NSRTFPboardType]) {
        if (attributedString == nil) {
            attributedString = [self selectedAttributedString];
        }
        if ([attributedString containsAttachments]) {
            attributedString = [attributedString _web_attributedStringByStrippingAttachmentCharacters];
        }
        NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
        [pasteboard setData:RTFData forType:NSRTFPboardType];
    }
    
    // Put plain string on the pasteboard.
    if ([types containsObject:NSStringPboardType]) {
        // Map &nbsp; to a plain old space because this is better for source code, other browsers do it,
        // and because HTML forces you to do this any time you want two spaces in a row.
        NSMutableString *s = [[self selectedString] mutableCopy];
        const unichar NonBreakingSpaceCharacter = 0xA0;
        NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&NonBreakingSpaceCharacter length:1];
        [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])];
        [pasteboard setString:s forType:NSStringPboardType];
        [s release];
    }
    
    if ([self _canSmartCopyOrDelete] && [types containsObject:WebSmartPastePboardType]) {
        [pasteboard setData:nil forType:WebSmartPastePboardType];
    }
}

- (void)_setMouseDownEvent:(NSEvent *)event
{
    ASSERT(!event || [event type] == NSLeftMouseDown || [event type] == NSRightMouseDown || [event type] == NSOtherMouseDown);

    if (event == _private->mouseDownEvent)
        return;

    [event retain];
    [_private->mouseDownEvent release];
    _private->mouseDownEvent = event;

    [_private->firstResponderTextViewAtMouseDownTime release];
    
    // The only code that checks this ivar only cares about NSTextViews. The code used to be more general,
    // but it caused reference cycles leading to world leaks (see 4557386). We should be able to eliminate
    // firstResponderTextViewAtMouseDownTime entirely when all the form controls are native widgets, because 
    // the only caller (in WebCore) will be unnecessary.
    if (event) {
        NSResponder *firstResponder = [[self window] firstResponder];
        if ([firstResponder isKindOfClass:[NSTextView class]])
            _private->firstResponderTextViewAtMouseDownTime = [firstResponder retain];
        else
            _private->firstResponderTextViewAtMouseDownTime = nil;
    } else
        _private->firstResponderTextViewAtMouseDownTime = nil;
}

- (void)_cancelUpdateActiveStateTimer
{
    if (_private->updateActiveStateTimer) {
        CFRunLoopTimerInvalidate(_private->updateActiveStateTimer);
        CFRelease(_private->updateActiveStateTimer);
        _private->updateActiveStateTimer = NULL;
    }
}

- (void)_cancelUpdateMouseoverTimer
{
    if (_private->updateMouseoverTimer) {
        CFRunLoopTimerInvalidate(_private->updateMouseoverTimer);
        CFRelease(_private->updateMouseoverTimer);
        _private->updateMouseoverTimer = NULL;
    }
}

- (WebHTMLView *)_topHTMLView
{
    // FIXME: this can fail if the dataSource is nil, which happens when the WebView is tearing down from the window closing.
    WebHTMLView *view = (WebHTMLView *)[[[[_private->dataSource _webView] mainFrame] frameView] documentView];
    ASSERT(view);
    ASSERT([view isKindOfClass:[WebHTMLView class]]);
    return view;
}

- (BOOL)_isTopHTMLView
{
    // FIXME: this should be a cached boolean that doesn't rely on _topHTMLView since that can fail (see _topHTMLView).
    return self == [self _topHTMLView];
}

@end

@implementation WebHTMLView (WebPrivate)

+ (NSArray *)supportedMIMETypes
{
    return [WebHTMLRepresentation supportedMIMETypes];
}

+ (NSArray *)supportedImageMIMETypes
{
    return [WebHTMLRepresentation supportedImageMIMETypes];
}

+ (NSArray *)supportedNonImageMIMETypes
{
    return [WebHTMLRepresentation supportedNonImageMIMETypes];
}

+ (NSArray *)unsupportedTextMIMETypes
{
    return [NSArray arrayWithObjects:
        @"text/calendar",       // iCal
        @"text/x-calendar",
        @"text/x-vcalendar",
        @"text/vcalendar",
        @"text/vcard",          // vCard
        @"text/x-vcard",
        @"text/directory",
        @"text/ldif",           // Netscape Address Book
        @"text/qif",            // Quicken
        @"text/x-qif",
        @"text/x-csv",          // CSV (for Address Book and Microsoft Outlook)
        @"text/x-vcf",          // vCard type used in Sun affinity app
        @"text/rtf",            // Rich Text Format
        nil];
}

+ (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent
{
    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
        location:[[flagsChangedEvent window] convertScreenToBase:[NSEvent mouseLocation]]
        modifierFlags:[flagsChangedEvent modifierFlags]
        timestamp:[flagsChangedEvent timestamp]
        windowNumber:[flagsChangedEvent windowNumber]
        context:[flagsChangedEvent context]
        eventNumber:0 clickCount:0 pressure:0];

    // Pretend it's a mouse move.
    [[NSNotificationCenter defaultCenter]
        postNotificationName:WKMouseMovedNotification() object:self
        userInfo:[NSDictionary dictionaryWithObject:fakeEvent forKey:@"NSEvent"]];
}

- (void)_updateMouseoverWithFakeEvent
{
    [self _cancelUpdateMouseoverTimer];
    
    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
        location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
        modifierFlags:[[NSApp currentEvent] modifierFlags]
        timestamp:[NSDate timeIntervalSinceReferenceDate]
        windowNumber:[[self window] windowNumber]
        context:[[NSApp currentEvent] context]
        eventNumber:0 clickCount:0 pressure:0];
    
    [self _updateMouseoverWithEvent:fakeEvent];
}

static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info)
{
    WebHTMLView *view = (WebHTMLView *)info;
    
    [view _updateMouseoverWithFakeEvent];
}

- (void)_frameOrBoundsChanged
{
    if (!NSEqualSizes(_private->lastLayoutSize, [(NSClipView *)[self superview] documentVisibleRect].size)) {
        [self setNeedsLayout:YES];
        [self setNeedsDisplay:YES];
        [_private->compController endRevertingChange:NO moveLeft:NO];
    }

    NSPoint origin = [[self superview] bounds].origin;
    if (!NSEqualPoints(_private->lastScrollPosition, origin)) {
        [[self _bridge] sendScrollEvent];
        [_private->compController endRevertingChange:NO moveLeft:NO];
        
        WebView *webView = [self _webView];
        [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[self _frameView]];
    }
    _private->lastScrollPosition = origin;

    if (!_private->updateMouseoverTimer) {
        CFRunLoopTimerContext context = { 0, self, NULL, NULL, NULL };
        _private->updateMouseoverTimer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 0, 0, 0,
                                                              _updateMouseoverTimerCallback, &context);
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), _private->updateMouseoverTimer, kCFRunLoopDefaultMode);
    }
}

- (void)_setAsideSubviews
{
    ASSERT(!_private->subviewsSetAside);
    ASSERT(_private->savedSubviews == nil);
    _private->savedSubviews = _subviews;
    _subviews = nil;
    _private->subviewsSetAside = YES;
 }
 
 - (void)_restoreSubviews
 {
    ASSERT(_private->subviewsSetAside);
    ASSERT(_subviews == nil);
    _subviews = _private->savedSubviews;
    _private->savedSubviews = nil;
    _private->subviewsSetAside = NO;
}

#ifdef BUILDING_ON_TIGER

// This is called when we are about to draw, but before our dirty rect is propagated to our ancestors.
// That's the perfect time to do a layout, except that ideally we'd want to be sure that we're dirty
// before doing it. As a compromise, when we're opaque we do the layout only when actually asked to
// draw, but when we're transparent we do the layout at this stage so views behind us know that they
// need to be redrawn (in case the layout causes some things to get dirtied).
- (void)_propagateDirtyRectsToOpaqueAncestors
{
    if (![[self _webView] drawsBackground])
        [self _web_layoutIfNeededRecursive];
    [super _propagateDirtyRectsToOpaqueAncestors];
}

#else

- (void)viewWillDraw
{
    // On window close we will be called when the datasource is nil, then hit an assert in _topHTMLView
    // So check if the dataSource is nil before calling [self _isTopHTMLView], this can be removed
    // once the FIXME in _isTopHTMLView is fixed.
    if (_private->dataSource && [self _isTopHTMLView])
        [self _web_layoutIfNeededRecursive];
    [super viewWillDraw];
}

#endif

// Don't let AppKit even draw subviews. We take care of that.
- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView
{
    // This helps when we print as part of a larger print process.
    // If the WebHTMLView itself is what we're printing, then we will never have to do this.
    BOOL wasInPrintingMode = _private->printing;
    BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
    if (wasInPrintingMode != isPrinting) {
        if (isPrinting)
            [self _web_setPrintingModeRecursive];
        else
            [self _web_clearPrintingModeRecursive];
    }

#ifdef BUILDING_ON_TIGER

    // Because Tiger does not have viewWillDraw we need to do layout here.
    [self _web_layoutIfNeededRecursive];
    [_subviews makeObjectsPerformSelector:@selector(_propagateDirtyRectsToOpaqueAncestors)];

#endif

    [self _setAsideSubviews];
    [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView];
    [self _restoreSubviews];

    if (wasInPrintingMode != isPrinting) {
        if (wasInPrintingMode)
            [self _web_setPrintingModeRecursive];
        else
            [self _web_clearPrintingModeRecursive];
    }
}

// Don't let AppKit even draw subviews. We take care of that.
- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect
{
    BOOL needToSetAsideSubviews = !_private->subviewsSetAside;

    BOOL wasInPrintingMode = _private->printing;
    BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];

    if (needToSetAsideSubviews) {
        // This helps when we print as part of a larger print process.
        // If the WebHTMLView itself is what we're printing, then we will never have to do this.
        if (wasInPrintingMode != isPrinting) {
            if (isPrinting)
                [self _web_setPrintingModeRecursive];
            else
                [self _web_clearPrintingModeRecursive];
        }

#ifdef BUILDING_ON_TIGER

        // Because Tiger does not have viewWillDraw we need to do layout here.
        NSRect boundsBeforeLayout = [self bounds];
        if (!NSIsEmptyRect(visRect))
            [self _web_layoutIfNeededRecursive];

        // If layout changes the view's bounds, then we need to recompute the visRect.
        // That's because the visRect passed to us was based on the bounds at the time
        // we were called. This method is only displayed to draw "all", so it's safe
        // to just call visibleRect to compute the entire rectangle.
        if (!NSEqualRects(boundsBeforeLayout, [self bounds]))
            visRect = [self visibleRect];

#endif

        [self _setAsideSubviews];
    }

    [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect];

    if (needToSetAsideSubviews) {
        if (wasInPrintingMode != isPrinting) {
            if (wasInPrintingMode)
                [self _web_setPrintingModeRecursive];
            else
                [self _web_clearPrintingModeRecursive];
        }

        [self _restoreSubviews];
    }
}

- (BOOL)_insideAnotherHTMLView
{
    return self != [self _topHTMLView];
}

- (void)scrollPoint:(NSPoint)point
{
    // Since we can't subclass NSTextView to do what we want, we have to second guess it here.
    // If we get called during the handling of a key down event, we assume the call came from
    // NSTextView, and ignore it and use our own code to decide how to page up and page down
    // We are smarter about how far to scroll, and we have "superview scrolling" logic.
    NSEvent *event = [[self window] currentEvent];
    if ([event type] == NSKeyDown) {
        const unichar pageUp = NSPageUpFunctionKey;
        if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageUp length:1]].length == 1) {
            [self tryToPerform:@selector(scrollPageUp:) with:nil];
            return;
        }
        const unichar pageDown = NSPageDownFunctionKey;
        if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageDown length:1]].length == 1) {
            [self tryToPerform:@selector(scrollPageDown:) with:nil];
            return;
        }
    }
    
    [super scrollPoint:point];
}

- (NSView *)hitTest:(NSPoint)point
{
    // WebHTMLView objects handle all events for objects inside them.
    // To get those events, we prevent hit testing from AppKit.

    // But there are three exceptions to this:
    //   1) For right mouse clicks and control clicks we don't yet have an implementation
    //      that works for nested views, so we let the hit testing go through the
    //      standard NSView code path (needs to be fixed, see bug 4361618).
    //   2) Java depends on doing a hit test inside it's mouse moved handling,
    //      so we let the hit testing go through the standard NSView code path
    //      when the current event is a mouse move (except when we are calling
    //      from _updateMouseoverWithEvent, so we have to use a global,
    //      forceWebHTMLViewHitTest, for that)
    //   3) The acceptsFirstMouse: and shouldDelayWindowOrderingForEvent: methods
    //      both need to figure out which view to check with inside the WebHTMLView.
    //      They use a global to change the behavior of hitTest: so they can get the
    //      right view. The global is forceNSViewHitTest and the method they use to
    //      do the hit testing is _hitViewForEvent:. (But this does not work correctly
    //      when there is HTML overlapping the view, see bug 4361626)
    //   4) NSAccessibilityHitTest relies on this for checking the cursor position.
    //      Our check for that is whether the event is NSFlagsChanged.  This works
    //      for VoiceOver's cntl-opt-f5 command (move focus to item under cursor)
    //      and Dictionary's cmd-cntl-D (open dictionary popup for item under cursor).
    //      This is of course a hack.

    BOOL captureHitsOnSubviews;
    if (forceNSViewHitTest)
        captureHitsOnSubviews = NO;
    else if (forceWebHTMLViewHitTest)
        captureHitsOnSubviews = YES;
    else {
        NSEvent *event = [[self window] currentEvent];
        captureHitsOnSubviews = !([event type] == NSMouseMoved
            || [event type] == NSRightMouseDown
            || ([event type] == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask) != 0)
            || [event type] == NSFlagsChanged);
    }

    if (!captureHitsOnSubviews)
        return [super hitTest:point];
    if ([[self superview] mouse:point inRect:[self frame]])
        return self;
    return nil;
}

- (void)_clearLastHitViewIfSelf
{
    if (lastHitView == self)
        lastHitView = nil;
}

- (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside
{
    ASSERT(_private->trackingRectOwner == nil);
    _private->trackingRectOwner = owner;
    _private->trackingRectUserData = data;
    return TRACKING_RECT_TAG;
}

- (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag
{
    ASSERT(tag == 0 || tag == TRACKING_RECT_TAG);
    ASSERT(_private->trackingRectOwner == nil);
    _private->trackingRectOwner = owner;
    _private->trackingRectUserData = data;
    return TRACKING_RECT_TAG;
}

- (void)_addTrackingRects:(NSRect *)rects owner:(id)owner userDataList:(void **)userDataList assumeInsideList:(BOOL *)assumeInsideList trackingNums:(NSTrackingRectTag *)trackingNums count:(int)count
{
    ASSERT(count == 1);
    ASSERT(trackingNums[0] == 0 || trackingNums[0] == TRACKING_RECT_TAG);
    ASSERT(_private->trackingRectOwner == nil);
    _private->trackingRectOwner = owner;
    _private->trackingRectUserData = userDataList[0];
    trackingNums[0] = TRACKING_RECT_TAG;
}

- (void)removeTrackingRect:(NSTrackingRectTag)tag
{
    if (tag == 0)
        return;
    
    if (_private && (tag == TRACKING_RECT_TAG)) {
        _private->trackingRectOwner = nil;
        return;
    }
    
    if (_private && (tag == _private->lastToolTipTag)) {
        [super removeTrackingRect:tag];
        _private->lastToolTipTag = 0;
        return;
    }
    
    // If any other tracking rect is being removed, we don't know how it was created
    // and it's possible there's a leak involved (see 3500217)
    ASSERT_NOT_REACHED();
}

- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count
{
    int i;
    for (i = 0; i < count; ++i) {
        int tag = tags[i];
        if (tag == 0)
            continue;
        ASSERT(tag == TRACKING_RECT_TAG);
        if (_private != nil) {
            _private->trackingRectOwner = nil;
        }
    }
}

- (void)_sendToolTipMouseExited
{
    // Nothing matters except window, trackingNumber, and userData.
    NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
        location:NSMakePoint(0, 0)
        modifierFlags:0
        timestamp:0
        windowNumber:[[self window] windowNumber]
        context:NULL
        eventNumber:0
        trackingNumber:TRACKING_RECT_TAG
        userData:_private->trackingRectUserData];
    [_private->trackingRectOwner mouseExited:fakeEvent];
}

- (void)_sendToolTipMouseEntered
{
    // Nothing matters except window, trackingNumber, and userData.
    NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
        location:NSMakePoint(0, 0)
        modifierFlags:0
        timestamp:0
        windowNumber:[[self window] windowNumber]
        context:NULL
        eventNumber:0
        trackingNumber:TRACKING_RECT_TAG
        userData:_private->trackingRectUserData];
    [_private->trackingRectOwner mouseEntered:fakeEvent];
}

- (void)_setToolTip:(NSString *)string
{
    NSString *toolTip = [string length] == 0 ? nil : string;
    NSString *oldToolTip = _private->toolTip;
    if ((toolTip == nil || oldToolTip == nil) ? toolTip == oldToolTip : [toolTip isEqualToString:oldToolTip]) {
        return;
    }
    if (oldToolTip) {
        [self _sendToolTipMouseExited];
        [oldToolTip release];
    }
    _private->toolTip = [toolTip copy];
    if (toolTip) {
        // See radar 3500217 for why we remove all tooltips rather than just the single one we created.
        [self removeAllToolTips];
        NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
        _private->lastToolTipTag = [self addToolTipRect:wideOpenRect owner:self userData:NULL];
        [self _sendToolTipMouseEntered];
    }
}

- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
{
    return [[_private->toolTip copy] autorelease];
}

- (void)_updateMouseoverWithEvent:(NSEvent *)event
{
    if (_private->closed)
        return;

    NSView *contentView = [[event window] contentView];
    NSPoint locationForHitTest = [[contentView superview] convertPoint:[event locationInWindow] fromView:nil];
    
    forceWebHTMLViewHitTest = YES;
    NSView *hitView = [contentView hitTest:locationForHitTest];
    forceWebHTMLViewHitTest = NO;
    
    WebHTMLView *view = nil;
    if ([hitView isKindOfClass:[WebHTMLView class]] && ![[(WebHTMLView *)hitView _webView] isHoverFeedbackSuspended])
        view = (WebHTMLView *)hitView;    

    if (view)
        [view retain];

    if (lastHitView != view && lastHitView && [lastHitView _frame]) {
        // If we are moving out of a view (or frame), let's pretend the mouse moved
        // all the way out of that view. But we have to account for scrolling, because
        // khtml doesn't understand our clipping.
        NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect];
        float yScroll = visibleRect.origin.y;
        float xScroll = visibleRect.origin.x;

        event = [NSEvent mouseEventWithType:NSMouseMoved
                         location:NSMakePoint(-1 - xScroll, -1 - yScroll )
                         modifierFlags:[[NSApp currentEvent] modifierFlags]
                         timestamp:[NSDate timeIntervalSinceReferenceDate]
                         windowNumber:[[view window] windowNumber]
                         context:[[NSApp currentEvent] context]
                         eventNumber:0 clickCount:0 pressure:0];
        if (Frame* lastHitCoreFrame = core([lastHitView _frame]))
            lastHitCoreFrame->eventHandler()->mouseMoved(event);
    }

    lastHitView = view;

    if (view) {
        if (Frame* coreFrame = core([view _frame]))
            coreFrame->eventHandler()->mouseMoved(event);

        [view release];
    }
}

// keep in sync with WebPasteboardHelper::insertablePasteboardTypes
+ (NSArray *)_insertablePasteboardTypes
{
    static NSArray *types = nil;
    if (!types) {
        types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType,
            NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSURLPboardType, 
            NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, NSColorPboardType, nil];
        CFRetain(types);
    }
    return types;
}

+ (NSArray *)_selectionPasteboardTypes
{
    // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423).
    return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
}

- (NSImage *)_dragImageForURL:(NSString*)urlString withLabel:(NSString*)label
{
    BOOL drawURLString = YES;
    BOOL clipURLString = NO, clipLabelString = NO;
    
    if (!label) {
        drawURLString = NO;
        label = urlString;
    }
    
    NSFont *labelFont = [[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:DRAG_LINK_LABEL_FONT_SIZE]
                                                           toHaveTrait:NSBoldFontMask];
    NSFont *urlFont = [NSFont systemFontOfSize: DRAG_LINK_URL_FONT_SIZE];
    NSSize labelSize;
    labelSize.width = [label _web_widthWithFont: labelFont];
    labelSize.height = [labelFont ascender] - [labelFont descender];
    if (labelSize.width > MAX_DRAG_LABEL_WIDTH){
        labelSize.width = MAX_DRAG_LABEL_WIDTH;
        clipLabelString = YES;
    }
    
    NSSize imageSize, urlStringSize;
    imageSize.width = labelSize.width + DRAG_LABEL_BORDER_X * 2.0f;
    imageSize.height = labelSize.height + DRAG_LABEL_BORDER_Y * 2.0f;
    if (drawURLString) {
        urlStringSize.width = [urlString _web_widthWithFont: urlFont];
        urlStringSize.height = [urlFont ascender] - [urlFont descender];
        imageSize.height += urlStringSize.height;
        if (urlStringSize.width > MAX_DRAG_LABEL_WIDTH) {
            imageSize.width = MAX(MAX_DRAG_LABEL_WIDTH + DRAG_LABEL_BORDER_X * 2.0f, MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP);
            clipURLString = YES;
        } else {
            imageSize.width = MAX(labelSize.width + DRAG_LABEL_BORDER_X * 2.0f, urlStringSize.width + DRAG_LABEL_BORDER_X * 2.0f);
        }
    }
    NSImage *dragImage = [[[NSImage alloc] initWithSize: imageSize] autorelease];
    [dragImage lockFocus];
    
    [[NSColor colorWithCalibratedRed: 0.7f green: 0.7f blue: 0.7f alpha: 0.8f] set];
    
    // Drag a rectangle with rounded corners/
    NSBezierPath *path = [NSBezierPath bezierPath];
    [path appendBezierPathWithOvalInRect: NSMakeRect(0.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)];
    [path appendBezierPathWithOvalInRect: NSMakeRect(0, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)];
    [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)];
    [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)];
    
    [path appendBezierPathWithRect: NSMakeRect(DRAG_LABEL_RADIUS, 0.0f, imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height)];
    [path appendBezierPathWithRect: NSMakeRect(0.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 10.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)];
    [path appendBezierPathWithRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS - 20.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 20.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)];
    [path fill];
    
    NSColor *topColor = [NSColor colorWithCalibratedWhite:0.0f alpha:0.75f];
    NSColor *bottomColor = [NSColor colorWithCalibratedWhite:1.0f alpha:0.5f];
    if (drawURLString) {
        if (clipURLString)
            urlString = [WebStringTruncator centerTruncateString: urlString toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:urlFont];
        
        [urlString _web_drawDoubledAtPoint:NSMakePoint(DRAG_LABEL_BORDER_X, DRAG_LABEL_BORDER_Y - [urlFont descender]) 
                              withTopColor:topColor bottomColor:bottomColor font:urlFont];
    }
    
    if (clipLabelString)
        label = [WebStringTruncator rightTruncateString: label toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:labelFont];
    [label _web_drawDoubledAtPoint:NSMakePoint (DRAG_LABEL_BORDER_X, imageSize.height - DRAG_LABEL_BORDER_Y_OFFSET - [labelFont pointSize])
                      withTopColor:topColor bottomColor:bottomColor font:labelFont];
    
    [dragImage unlockFocus];
    
    return dragImage;
}

- (NSImage *)_dragImageForLinkElement:(NSDictionary *)element
{
    NSURL *linkURL = [element objectForKey: WebElementLinkURLKey];
    
    NSString *label = [element objectForKey: WebElementLinkLabelKey];
    NSString *urlString = [linkURL _web_userVisibleString];
    return [self _dragImageForURL:urlString withLabel:label];
}

- (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard
{
    [self setPromisedDragTIFFDataSource:0];
}

- (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type
{
    if ([type isEqual:NSRTFDPboardType] && [[pasteboard types] containsObject:WebArchivePboardType]) {
        WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]];
        [pasteboard _web_writePromisedRTFDFromArchive:archive containsImage:[[pasteboard types] containsObject:NSTIFFPboardType]];
        [archive release];
    } else if ([type isEqual:NSTIFFPboardType] && [self promisedDragTIFFDataSource]) {
        if (Image* image = [self promisedDragTIFFDataSource]->image())
            [pasteboard setData:(NSData *)image->getTIFFRepresentation() forType:NSTIFFPboardType];
        [self setPromisedDragTIFFDataSource:0];
    }
}

- (void)_handleAutoscrollForMouseDragged:(NSEvent *)event 
{ 
    [self autoscroll:event]; 
    [self _startAutoscrollTimer:event]; 
} 

- (WebPluginController *)_pluginController
{
    return _private->pluginController;
}

- (void)_web_setPrintingModeRecursive
{
    [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];
    [super _web_setPrintingModeRecursive];
}

- (void)_web_clearPrintingModeRecursive
{
    [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];
    [super _web_clearPrintingModeRecursive];
}

- (void)_web_setPrintingModeRecursiveAndAdjustViewSize
{
    [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES];
    [super _web_setPrintingModeRecursiveAndAdjustViewSize];
}

- (void)_layoutForPrinting
{
    // Set printing mode temporarily so we can adjust the size of the view. This will allow
    // AppKit's pagination code to use the correct height for the page content. Leaving printing
    // mode on indefinitely would interfere with Mail's printing mechanism (at least), so we just
    // turn it off again after adjusting the size.
    [self _web_setPrintingModeRecursiveAndAdjustViewSize];
    [self _web_clearPrintingModeRecursive];
}

- (void)_layoutIfNeeded
{
    ASSERT(!_private->subviewsSetAside);

    if ([[self _bridge] needsLayout])
        _private->needsLayout = YES;
    if (_private->needsToApplyStyles || _private->needsLayout)
        [self layout];
}

- (void)_web_layoutIfNeededRecursive
{
    [self _layoutIfNeeded];
    [super _web_layoutIfNeededRecursive];
}

- (void)_startAutoscrollTimer: (NSEvent *)triggerEvent
{
    if (_private->autoscrollTimer == nil) {
        _private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL
            target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES] retain];
        _private->autoscrollTriggerEvent = [triggerEvent retain];
    }
}

// FIXME: _selectionRect is deprecated in favor of selectionRect, which is in protocol WebDocumentSelection.
// We can't remove this yet because it's still in use by Mail.
- (NSRect)_selectionRect
{
    return [self selectionRect];
}

- (void)_stopAutoscrollTimer
{
    NSTimer *timer = _private->autoscrollTimer;
    _private->autoscrollTimer = nil;
    [_private->autoscrollTriggerEvent release];
    _private->autoscrollTriggerEvent = nil;
    [timer invalidate];
    [timer release];
}

- (void)_autoscroll
{
    // Guarantee that the autoscroll timer is invalidated, even if we don't receive
    // a mouse up event.
    BOOL isStillDown = CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft);   
    if (!isStillDown){
        [self _stopAutoscrollTimer];
        return;
    }

    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
        location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
        modifierFlags:[[NSApp currentEvent] modifierFlags]
        timestamp:[NSDate timeIntervalSinceReferenceDate]
        windowNumber:[[self window] windowNumber]
        context:[[NSApp currentEvent] context]
        eventNumber:0 clickCount:0 pressure:0];
    [self mouseDragged:fakeEvent];
}

- (BOOL)_canEdit
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame && coreFrame->editor()->canEdit();
}

- (BOOL)_canEditRichly
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame && coreFrame->editor()->canEditRichly();
}

- (BOOL)_canAlterCurrentSelection
{
    return [self _hasSelectionOrInsertionPoint] && [self _isEditable];
}

- (BOOL)_hasSelection
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame && coreFrame->selectionController()->isRange();
}

- (BOOL)_hasSelectionOrInsertionPoint
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame && coreFrame->selectionController()->isCaretOrRange();
}

- (BOOL)_hasInsertionPoint
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame && coreFrame->selectionController()->isCaret();
}

- (BOOL)_isEditable
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame && coreFrame->selectionController()->isContentEditable();
}

- (BOOL)_transparentBackground
{
    return _private->transparentBackground;
}

- (void)_setTransparentBackground:(BOOL)f
{
    _private->transparentBackground = f;
}

- (NSImage *)_selectionDraggingImage
{
    if ([self _hasSelection]) {
        NSImage *dragImage = core([self _frame])->selectionImage();
        [dragImage _web_dissolveToFraction:WebDragImageAlpha];
        return dragImage;
    }
    return nil;
}

- (NSRect)_selectionDraggingRect
{
    // Mail currently calls this method. We can eliminate it when Mail no longer calls it.
    return [self selectionRect];
}

- (DOMNode *)_insertOrderedList
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame ? kit(coreFrame->editor()->insertOrderedList().get()) : nil;
}

- (DOMNode *)_insertUnorderedList
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame ? kit(coreFrame->editor()->insertUnorderedList().get()) : nil;
}

- (BOOL)_canIncreaseSelectionListLevel
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame && coreFrame->editor()->canIncreaseSelectionListLevel();
}

- (BOOL)_canDecreaseSelectionListLevel
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame && coreFrame->editor()->canDecreaseSelectionListLevel();
}

- (DOMNode *)_increaseSelectionListLevel
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevel().get()) : nil;
}

- (DOMNode *)_increaseSelectionListLevelOrdered
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevelOrdered().get()) : nil;
}

- (DOMNode *)_increaseSelectionListLevelUnordered
{
    Frame* coreFrame = core([self _frame]);
    return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevelUnordered().get()) : nil;
}

- (void)_decreaseSelectionListLevel
{
    Frame* coreFrame = core([self _frame]);
    if (coreFrame)
        coreFrame->editor()->decreaseSelectionListLevel();
}

- (void)_setHighlighter:(id<WebHTMLHighlighter>)highlighter ofType:(NSString*)type
{
    if (!_private->highlighters)
        _private->highlighters = [[NSMutableDictionary alloc] init];
    [_private->highlighters setObject:highlighter forKey:type];
}

- (void)_removeHighlighterOfType:(NSString*)type
{
    [_private->highlighters removeObjectForKey:type];
}

- (BOOL)_web_firstResponderCausesFocusDisplay
{
    return [[self window] firstResponder] == self || [[self window] firstResponder] == [self _frameView];
}

- (void)_updateActiveState
{
    [self _cancelUpdateActiveStateTimer];

    // This method does the job of updating the view based on the view's firstResponder-ness and
    // the window key-ness of the window containing this view. This involves four kinds of 
    // drawing updates right now. 
    // 
    // The four display attributes are as follows:
    // 
    // 1. The background color used to draw behind selected content (active | inactive color)
    // 2. Caret blinking (blinks | does not blink)
    // 3. The drawing of a focus ring around links in web pages.
    // 4. Changing the tint of controls from clear to aqua/graphite and vice versa
    //
    // Also, this is responsible for letting the bridge know if the window has gained or lost focus
    // so we can send focus and blur events.

    Frame* frame = core([self _frame]);
    if (!frame)
        return;
    
    Page* page = frame->page();
    if (!page)
        return;

    NSWindow *window = [self window];
    BOOL windowIsKey = [window isKeyWindow];
    BOOL windowOrSheetIsKey = windowIsKey || [[window attachedSheet] isKeyWindow];

    BOOL isActive = !_private->resigningFirstResponder && windowIsKey && [self _web_firstResponderCausesFocusDisplay];
    frame->setIsActive(isActive);            

    Frame* focusedFrame = page->focusController()->focusedOrMainFrame();
    frame->setWindowHasFocus(frame == focusedFrame && windowOrSheetIsKey);
}

- (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard
{
    ASSERT([self _hasSelection]);
    NSArray *types = [self pasteboardTypesForSelection];

    // Don't write RTFD to the pasteboard when the copied attributed string has no attachments.
    NSAttributedString *attributedString = [self selectedAttributedString];
    NSMutableArray *mutableTypes = nil;
    if (![attributedString containsAttachments]) {
        mutableTypes = [types mutableCopy];
        [mutableTypes removeObject:NSRTFDPboardType];
        types = mutableTypes;
    }

    [pasteboard declareTypes:types owner:[self _topHTMLView]];
    [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:attributedString];
    [mutableTypes release];
}

- (void)close
{
    // Check for a nil _private here incase we were created with initWithCoder. In that case, the WebView is just throwing
    // out the archived WebHTMLView and recreating a new one if needed. So close dosen't need to do anything in that case.
    if (!_private || _private->closed)
        return;
    [self _clearLastHitViewIfSelf];
    // FIXME: This is slow; should remove individual observers instead.
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [_private->pluginController destroyAllPlugins];
    [_private->pluginController setDataSource:nil];
    // remove tooltips before clearing _private so removeTrackingRect: will work correctly
    [self removeAllToolTips];
    [_private clear];
    _private->closed = YES;
    Page* page = core([self _webView]);
    if (page)
        page->dragController()->setDraggingImageURL(KURL());
}

- (BOOL)_hasHTMLDocument
{
    Frame* coreFrame = core([self _frame]);
    if (!coreFrame)
        return NO;
    Document* document = coreFrame->document();
    return document && document->isHTMLDocument();
}

- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard
                                                 forType:(NSString *)pboardType
                                               inContext:(DOMRange *)context
                                            subresources:(NSArray **)subresources
{
    if (pboardType == WebArchivePboardType) {
        WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]];
        if (subresources)
            *subresources = [archive subresources];
        DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithArchive:archive];
        [archive release];
        return fragment;
    }
    if (pboardType == NSFilenamesPboardType)
        return [self _documentFragmentWithPaths:[pasteboard propertyListForType:NSFilenamesPboardType]];
        
    if (pboardType == NSHTMLPboardType) {
        NSString *HTMLString = [pasteboard stringForType:NSHTMLPboardType];
        // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785.
        if ([HTMLString hasPrefix:@"Version:"]) {
            NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch];
            if (range.location != NSNotFound)
                HTMLString = [HTMLString substringFromIndex:range.location];
        }
        if ([HTMLString length] == 0)
            return nil;
        
        return [[self _bridge] documentFragmentWithMarkupString:HTMLString baseURLString:nil];
    }

    // The _hasHTMLDocument clause here is a workaround for a bug in NSAttributedString: Radar 5052369.
    // If we call _documentFromRange on an XML document we'll get "setInnerHTML: method not found".
    // FIXME: Remove this once bug 5052369 is fixed.
    if ([self _hasHTMLDocument] && pboardType == NSRTFPboardType || pboardType == NSRTFDPboardType) {
        NSAttributedString *string = nil;
        if (pboardType == NSRTFDPboardType)
            string = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
        if (string == nil)
            string = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
        if (string == nil)
            return nil;
            
        NSDictionary *documentAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
            [[self class] _excludedElementsForAttributedStringConversion], NSExcludedElementsDocumentAttribute,
            self, @"WebResourceHandler", nil];
        NSArray *s;
        
        BOOL wasDeferringCallbacks = [[self _webView] defersCallbacks];
        if (!wasDeferringCallbacks)
            [[self _webView] setDefersCallbacks:YES];
            
        DOMDocumentFragment *fragment = [string _documentFromRange:NSMakeRange(0, [string length]) 
                                                          document:[[self _frame] DOMDocument] 
                                                documentAttributes:documentAttributes
                                                      subresources:&s];
        if (subresources)
            *subresources = s;
        
        NSEnumerator *e = [s objectEnumerator];
        WebResource *r;
        while ((r = [e nextObject]))
            [[self _dataSource] addSubresource:r];
        
        if (!wasDeferringCallbacks)
            [[self _webView] setDefersCallbacks:NO];
        
        [documentAttributes release];
        [string release];
        return fragment;
    }
    if (pboardType == NSTIFFPboardType) {
        WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSTIFFPboardType]
                                                              URL:uniqueURLWithRelativePart(@"image.tiff")
                                                         MIMEType:@"image/tiff" 
                                                 textEncodingName:nil
                                                        frameName:nil];
        DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
        [resource release];
        return fragment;
    }
    if (pboardType == NSPICTPboardType) {
        WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPICTPboardType]
                                                              URL:uniqueURLWithRelativePart(@"image.pict")
                                                         MIMEType:@"image/pict" 
                                                 textEncodingName:nil
                                                        frameName:nil];
        DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
        [resource release];
        return fragment;
    }
    if (pboardType == NSURLPboardType) {
        NSURL *URL = [NSURL URLFromPasteboard:pasteboard];
        DOMDocument* document = [[self _frame] DOMDocument];
        ASSERT(document);
        if (!document)
            return nil;
        DOMHTMLAnchorElement *anchor = (DOMHTMLAnchorElement *)[document createElement:@"a"];
        NSString *URLString = [URL _web_originalDataAsString];
        if ([URLString length] == 0)
            return nil;
        NSString *URLTitleString = [pasteboard stringForType:WebURLNamePboardType];
        DOMText *text = [document createTextNode:URLTitleString];
        [anchor setHref:URLString];
        [anchor appendChild:text];
        DOMDocumentFragment *fragment = [document createDocumentFragment];
        [fragment appendChild:anchor];
        return fragment;
    }
    if (pboardType == NSStringPboardType)
        return [[self _bridge] documentFragmentWithText:[pasteboard stringForType:NSStringPboardType]
                                              inContext:context];
                                              
    return nil;
}

@end

@implementation NSView (WebHTMLViewFileInternal)

- (void)_web_setPrintingModeRecursive
{
    [_subviews makeObjectsPerformSelector:@selector(_web_setPrintingModeRecursive)];
}

- (void)_web_setPrintingModeRecursiveAndAdjustViewSize
{
    [_subviews makeObjectsPerformSelector:@selector(_web_setPrintingModeRecursiveAndAdjustViewSize)];
}

- (void)_web_clearPrintingModeRecursive
{
    [_subviews makeObjectsPerformSelector:@selector(_web_clearPrintingModeRecursive)];
}

- (void)_web_layoutIfNeededRecursive
{
    [_subviews makeObjectsPerformSelector:@selector(_web_layoutIfNeededRecursive)];
}

@end

@implementation NSMutableDictionary (WebHTMLViewFileInternal)

- (void)_web_setObjectIfNotNil:(id)object forKey:(id)key
{
    if (object == nil) {
        [self removeObjectForKey:key];
    } else {
        [self setObject:object forKey:key];
    }
}

@end

#ifdef BUILDING_ON_TIGER

// The following is a workaround for
// <rdar://problem/3429631> window stops getting mouse moved events after first tooltip appears
// The trick is to define a category on NSToolTipPanel that implements setAcceptsMouseMovedEvents:.
// Since the category will be searched before the real class, we'll prevent the flag from being
// set on the tool tip panel.

@interface NSToolTipPanel : NSPanel
@end

@interface NSToolTipPanel (WebHTMLViewFileInternal)
@end

@implementation NSToolTipPanel (WebHTMLViewFileInternal)

- (void)setAcceptsMouseMovedEvents:(BOOL)flag
{
    // Do nothing, preventing the tool tip panel from trying to accept mouse-moved events.
}

@end

#endif

@interface NSArray (WebHTMLView)
- (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object;
@end

@implementation WebHTMLView

+ (void)initialize
{
    [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes] 
                             returnTypes:[[self class] _insertablePasteboardTypes]];
    _NSInitializeKillRing();
#ifndef BUILDING_ON_TIGER
    WebCoreObjCFinalizeOnMainThread(self);
#endif
}

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (!self)
        return nil;
    
    [self setFocusRingType:NSFocusRingTypeNone];
    
    // Make all drawing go through us instead of subviews.
    [self _setDrawsOwnDescendants:YES];
    
    _private = [[WebHTMLViewPrivate alloc] init];

    _private->pluginController = [[WebPluginController alloc] initWithDocumentView:self];
    _private->needsLayout = YES;
    
    return self;
}

- (void)dealloc
{
    // We can't assert that close has already been called because
    // this view can be removed from it's superview, even though
    // it could be needed later, so close if needed.
    [self close];
    [_private release];
    _private = nil;
    [super dealloc];
}

- (void)finalize
{
    ASSERT_MAIN_THREAD();
    // We can't assert that close has already been called because
    // this view can be removed from it's superview, even though
    // it could be needed later, so close if needed.
    [self close];
    [super finalize];
}

// Returns YES if the delegate returns YES (so we should do no more work).
- (BOOL)callDelegateDoCommandBySelectorIfNeeded:(SEL)selector
{
    BOOL callerAlreadyCalledDelegate = _private->selectorForDoCommandBySelector == selector;
    _private->selectorForDoCommandBySelector = 0;
    if (callerAlreadyCalledDelegate)
        return NO;

    WebView *webView = [self _webView];
    id editingDelegate = [webView editingDelegate];
    if (![editingDelegate respondsToSelector:@selector(webView:doCommandBySelector:)])
        return NO;

    return [editingDelegate webView:webView doCommandBySelector:selector];
}

- (void)callWebCoreCommand:(SEL)selector
{
    if ([self callDelegateDoCommandBySelectorIfNeeded:selector])
        return;

    Frame* coreFrame = core([self _frame]);
    if (!coreFrame)
        return;

    // Capitalize the first letter of the selector, since we use capitalized command
    // names in the Editor object (why?). And remove the trailing colon.
    const char* selectorName = sel_getName(selector);
    size_t selectorNameLength = strlen(selectorName);
    ASSERT(selectorNameLength >= 2);
    ASSERT(selectorName[selectorNameLength - 1] == ':');
    Vector<char, 256> commandName(selectorNameLength - 1 + 1);
    commandName[0] = toupper(selectorName[0]);
    memcpy(&commandName[1], &selectorName[1], selectorNameLength - 2);
    commandName[selectorNameLength - 1] = 0;

    coreFrame->editor()->execCommand(commandName.data());
}

// These commands are forwarded to the Editor object in WebCore.
// Ideally we'd do this for all editing commands; more of the code
// should be moved from here to there, and more commands should be
// added to this list.

#define WEBCORE_COMMAND(command) - (void)command:(id)sender { [self callWebCoreCommand:_cmd]; }

WEBCORE_COMMAND(deleteWordBackward)
WEBCORE_COMMAND(deleteWordForward)
WEBCORE_COMMAND(insertBacktab)
WEBCORE_COMMAND(insertLineBreak)
WEBCORE_COMMAND(insertNewline)
WEBCORE_COMMAND(insertTab)
WEBCORE_COMMAND(moveBackward)
WEBCORE_COMMAND(moveBackwardAndModifySelection)
WEBCORE_COMMAND(moveDown)
WEBCORE_COMMAND(moveDownAndModifySelection)
WEBCORE_COMMAND(moveForward)
WEBCORE_COMMAND(moveForwardAndModifySelection)
WEBCORE_COMMAND(moveLeft)
WEBCORE_COMMAND(moveLeftAndModifySelection)
WEBCORE_COMMAND(moveParagraphBackwardAndModifySelection)
WEBCORE_COMMAND(moveParagraphForwardAndModifySelection)
WEBCORE_COMMAND(moveRight)
WEBCORE_COMMAND(moveRightAndModifySelection)
WEBCORE_COMMAND(moveToBeginningOfDocument)
WEBCORE_COMMAND(moveToBeginningOfDocumentAndModifySelection)
WEBCORE_COMMAND(moveToBeginningOfLine)
WEBCORE_COMMAND(moveToBeginningOfLineAndModifySelection)
WEBCORE_COMMAND(moveToBeginningOfParagraph)
WEBCORE_COMMAND(moveToBeginningOfParagraphAndModifySelection)
WEBCORE_COMMAND(moveToBeginningOfSentence)
WEBCORE_COMMAND(moveToBeginningOfSentenceAndModifySelection)
WEBCORE_COMMAND(moveToEndOfDocument)
WEBCORE_COMMAND(moveToEndOfDocumentAndModifySelection)
WEBCORE_COMMAND(moveToEndOfLine)
WEBCORE_COMMAND(moveToEndOfLineAndModifySelection)
WEBCORE_COMMAND(moveToEndOfParagraph)
WEBCORE_COMMAND(moveToEndOfParagraphAndModifySelection)
WEBCORE_COMMAND(moveToEndOfSentence)
WEBCORE_COMMAND(moveToEndOfSentenceAndModifySelection)
WEBCORE_COMMAND(moveUp)
WEBCORE_COMMAND(moveUpAndModifySelection)
WEBCORE_COMMAND(moveWordBackward)
WEBCORE_COMMAND(moveWordBackwardAndModifySelection)
WEBCORE_COMMAND(moveWordForward)
WEBCORE_COMMAND(moveWordForwardAndModifySelection)
WEBCORE_COMMAND(moveWordLeft)
WEBCORE_COMMAND(moveWordLeftAndModifySelection)
WEBCORE_COMMAND(moveWordRight)
WEBCORE_COMMAND(moveWordRightAndModifySelection)

#undef WEBCORE_COMMAND

#define COMMAND_PROLOGUE if ([self callDelegateDoCommandBySelectorIfNeeded:_cmd]) return;

- (IBAction)takeFindStringFromSelection:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _hasSelection]) {
        NSBeep();
        return;
    }

    [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self];
}

- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types
{
    [pasteboard declareTypes:types owner:[self _topHTMLView]];
    [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard];
    return YES;
}

- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard
{
    Frame* coreFrame = core([self _frame]);
    if (!coreFrame)
        return NO;
    if (coreFrame->selectionController()->isContentRichlyEditable())
        [self _pasteWithPasteboard:pasteboard allowPlainText:YES];
    else
        [self _pasteAsPlainTextWithPasteboard:pasteboard];
    return YES;
}

- (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
{
    if (sendType != nil && [[self pasteboardTypesForSelection] containsObject:sendType] && [self _hasSelection]) {
        return self;
    } else if (returnType != nil && [[[self class] _insertablePasteboardTypes] containsObject:returnType] && [self _isEditable]) {
        return self;
    }
    return [[self nextResponder] validRequestorForSendType:sendType returnType:returnType];
}

- (void)selectAll:(id)sender
{
    COMMAND_PROLOGUE

    [self selectAll];
}

// jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari
// was using the old jumpToSelection selector in its menu. Newer versions of Safari will us the
// selector centerSelectionInVisibleArea. We'll leave this old selector in place for two reasons:
// (1) compatibility between older Safari and newer WebKit; (2) other WebKit-based applications
// might be using the jumpToSelection: selector, and we don't want to break them.
- (void)jumpToSelection:(id)sender
{
    COMMAND_PROLOGUE

    if (Frame* coreFrame = core([self _frame]))
        coreFrame->revealSelection(RenderLayer::gAlignCenterAlways);
}

- (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item
{
    SEL action = [item action];
    Frame* frame = core([self _frame]);

    if (Document* doc = frame->document()) {
        if (doc->isPluginDocument())
            return NO;
        
        if (doc->isImageDocument()) {            
            if (action == @selector(copy:))
                return frame->loader()->isComplete();
        
            return NO;
        }
    }
    
    if (action == @selector(changeSpelling:)
            || action == @selector(_changeSpellingFromMenu:)
            || action == @selector(checkSpelling:)
            || action == @selector(complete:)
            || action == @selector(deleteBackward:)
            || action == @selector(deleteBackwardByDecomposingPreviousCharacter:)
            || action == @selector(deleteForward:)
            || action == @selector(deleteToBeginningOfLine:)
            || action == @selector(deleteToBeginningOfParagraph:)
            || action == @selector(deleteToEndOfLine:)
            || action == @selector(deleteToEndOfParagraph:)
            || action == @selector(deleteToMark:)
            || action == @selector(deleteWordBackward:)
            || action == @selector(deleteWordForward:)
            || action == @selector(insertBacktab:)
            || action == @selector(insertLineBreak:)
            || action == @selector(insertNewline:)
            || action == @selector(insertNewlineIgnoringFieldEditor:)
            || action == @selector(insertParagraphSeparator:)
            || action == @selector(insertTab:)
            || action == @selector(insertTabIgnoringFieldEditor:)
            || action == @selector(moveBackward:)
            || action == @selector(moveBackwardAndModifySelection:)
            || action == @selector(moveDown:)
            || action == @selector(moveDownAndModifySelection:)
            || action == @selector(moveForward:)
            || action == @selector(moveForwardAndModifySelection:)
            || action == @selector(moveLeft:)
            || action == @selector(moveLeftAndModifySelection:)
            || action == @selector(moveParagraphBackwardAndModifySelection:)
            || action == @selector(moveParagraphForwardAndModifySelection:)
            || action == @selector(moveRight:)
            || action == @selector(moveRightAndModifySelection:)
            || action == @selector(moveToBeginningOfDocument:)
            || action == @selector(moveToBeginningOfDocumentAndModifySelection:)
            || action == @selector(moveToBeginningOfSentence:)
            || action == @selector(moveToBeginningOfSentenceAndModifySelection:)
            || action == @selector(moveToBeginningOfLine:)
            || action == @selector(moveToBeginningOfLineAndModifySelection:)
            || action == @selector(moveToBeginningOfParagraph:)
            || action == @selector(moveToBeginningOfParagraphAndModifySelection:)
            || action == @selector(moveToEndOfDocument:)
            || action == @selector(moveToEndOfDocumentAndModifySelection:)
            || action == @selector(moveToEndOfSentence:)
            || action == @selector(moveToEndOfSentenceAndModifySelection:)
            || action == @selector(moveToEndOfLine:)
            || action == @selector(moveToEndOfLineAndModifySelection:)
            || action == @selector(moveToEndOfParagraph:)
            || action == @selector(moveToEndOfParagraphAndModifySelection:)
            || action == @selector(moveUp:)
            || action == @selector(moveUpAndModifySelection:)
            || action == @selector(moveWordBackward:)
            || action == @selector(moveWordBackwardAndModifySelection:)
            || action == @selector(moveWordForward:)
            || action == @selector(moveWordForwardAndModifySelection:)
            || action == @selector(moveWordLeft:)
            || action == @selector(moveWordLeftAndModifySelection:)
            || action == @selector(moveWordRight:)
            || action == @selector(moveWordRightAndModifySelection:)
            || action == @selector(pageDown:)
            || action == @selector(pageDownAndModifySelection:)
            || action == @selector(pageUp:)
            || action == @selector(pageUpAndModifySelection:)
            || action == @selector(pasteFont:)
            || action == @selector(transpose:)
            || action == @selector(yank:)
            || action == @selector(yankAndSelect:))
        return [self _canEdit];
    
    if (action == @selector(showGuessPanel:)) {
#ifndef BUILDING_ON_TIGER
        // Match OS X AppKit behavior for post-Tiger. Don't change Tiger behavior.
        NSMenuItem *menuItem = (NSMenuItem *)item;
        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
            BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
            [menuItem setTitle:panelShowing ? UI_STRING("Hide Spelling and Grammar", "menu item title") : UI_STRING("Show Spelling and Grammar", "menu item title")];
        }
#endif
        return [self _canEdit];
    }
    
    if (action == @selector(changeBaseWritingDirection:)) {
        NSMenuItem *menuItem = (NSMenuItem *)item;
        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
            NSWritingDirection writingDirection = static_cast<NSWritingDirection>([item tag]);
            if (writingDirection == NSWritingDirectionNatural) {
                [menuItem setState:NO];
                return NO;
            }
            DOMCSSStyleDeclaration* style = [self _emptyStyle];
            [style setDirection:writingDirection == NSWritingDirectionLeftToRight ? @"LTR" : @"RTL"];
            [menuItem setState:[[self _bridge] selectionHasStyle:style]];
        }
        return [self _canEdit];
    }
    
    if (action == @selector(toggleBaseWritingDirection:)) {
        NSMenuItem *menuItem = (NSMenuItem *)item;
        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
            DOMCSSStyleDeclaration* rtl = [self _emptyStyle];
            [rtl setDirection:@"RTL"];
            // Take control of the title of the menu item, instead of just checking/unchecking it because otherwise
            // we don't know what the check would mean.
            [menuItem setTitle:[[self _bridge] selectionHasStyle:rtl] ? UI_STRING("Left to Right", "Left to Right context menu item") : UI_STRING("Right to Left", "Right to Left context menu item")];
        }
        return [self _canEdit];
    } 
    
    if (action == @selector(alignCenter:)
            || action == @selector(alignLeft:)
            || action == @selector(alignJustified:)
            || action == @selector(alignRight:)
            || action == @selector(changeAttributes:)
            || action == @selector(changeColor:)        
            || action == @selector(changeFont:)
            || action == @selector(indent:)
            || action == @selector(outdent:))
        return [self _canEditRichly];
    
    if (action == @selector(capitalizeWord:)
               || action == @selector(lowercaseWord:)
               || action == @selector(uppercaseWord:))
        return [self _hasSelection] && [self _isEditable];
    
    if (action == @selector(centerSelectionInVisibleArea:)
               || action == @selector(jumpToSelection:)
               || action == @selector(copyFont:)
               || action == @selector(setMark:))
        return [self _hasSelection] || ([self _isEditable] && [self _hasInsertionPoint]);
    
    if (action == @selector(changeDocumentBackgroundColor:))
        return [[self _webView] isEditable] && [self _canEditRichly];
    
    if (action == @selector(copy:))
        return (frame && frame->editor()->canDHTMLCopy()) || frame->editor()->canCopy();
    
    if (action == @selector(cut:))
        return (frame && frame->editor()->canDHTMLCut()) || frame->editor()->canCut();
    
    if (action == @selector(delete:))
        return (frame && frame->editor()->canDelete());
    
    if (action == @selector(_ignoreSpellingFromMenu:)
            || action == @selector(_learnSpellingFromMenu:)
            || action == @selector(takeFindStringFromSelection:))
        return [self _hasSelection];
    
    if (action == @selector(paste:) || action == @selector(pasteAsPlainText:))
        return (frame && frame->editor()->canDHTMLPaste()) || frame->editor()->canPaste();
    
    if (action == @selector(pasteAsRichText:))
        return frame && (frame->editor()->canDHTMLPaste()
            || (frame->editor()->canPaste() && frame->selectionController()->isContentRichlyEditable()));
    
    if (action == @selector(performFindPanelAction:))
        // FIXME: Not yet implemented.
        return NO;
    
    if (action == @selector(selectToMark:)
            || action == @selector(swapWithMark:))
        return [self _hasSelectionOrInsertionPoint] && [[self _bridge] markDOMRange] != nil;
    
    if (action == @selector(subscript:)) {
        NSMenuItem *menuItem = (NSMenuItem *)item;
        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
            DOMCSSStyleDeclaration *style = [self _emptyStyle];
            [style setVerticalAlign:@"sub"];
            [menuItem setState:[[self _bridge] selectionHasStyle:style]];
        }
        return [self _canEditRichly];
    } 
    
    if (action == @selector(superscript:)) {
        NSMenuItem *menuItem = (NSMenuItem *)item;
        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
            DOMCSSStyleDeclaration *style = [self _emptyStyle];
            [style setVerticalAlign:@"super"];
            [menuItem setState:[[self _bridge] selectionHasStyle:style]];
        }
        return [self _canEditRichly];
    } 
    
    if (action == @selector(underline:)) {
        NSMenuItem *menuItem = (NSMenuItem *)item;
        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
            DOMCSSStyleDeclaration *style = [self _emptyStyle];
            [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""];
            [menuItem setState:[[self _bridge] selectionHasStyle:style]];
        }
        return [self _canEditRichly];
    } 
    
    if (action == @selector(unscript:)) {
        NSMenuItem *menuItem = (NSMenuItem *)item;
        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
            DOMCSSStyleDeclaration *style = [self _emptyStyle];
            [style setVerticalAlign:@"baseline"];
            [menuItem setState:[[self _bridge] selectionHasStyle:style]];
        }
        return [self _canEditRichly];
    } 
    
    if (action == @selector(_lookUpInDictionaryFromMenu:)) {
        return [self _hasSelection];
    } 
    
#ifndef BUILDING_ON_TIGER
    if (action == @selector(toggleGrammarChecking:)) {
        // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must validate 
        // the selector here because we implement it here, and we must implement it here because the AppKit 
        // code checks the first responder.
        BOOL checkMark = [self isGrammarCheckingEnabled];
        if ([(NSObject *)item isKindOfClass:[NSMenuItem class]]) {
            NSMenuItem *menuItem = (NSMenuItem *)item;
            [menuItem setState:checkMark ? NSOnState : NSOffState];
        }
        return YES;
    }
#endif
    
    return YES;
}

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
{
    BOOL result = [self validateUserInterfaceItemWithoutDelegate:item];

    id ud = [[self _webView] UIDelegate];
    if (ud && [ud respondsToSelector:@selector(webView:validateUserInterfaceItem:defaultValidation:)])
        return [ud webView:[self _webView] validateUserInterfaceItem:item defaultValidation:result];

    return result;
}

- (BOOL)acceptsFirstResponder
{
    // Don't accept first responder when we first click on this view.
    // We have to pass the event down through WebCore first to be sure we don't hit a subview.
    // Do accept first responder at any other time, for example from keyboard events,
    // or from calls back from WebCore once we begin mouse-down event handling.
    NSEvent *event = [NSApp currentEvent];
    if ([event type] == NSLeftMouseDown
            && !_private->handlingMouseDownEvent
            && NSPointInRect([event locationInWindow], [self convertRect:[self visibleRect] toView:nil])) {
        return NO;
    }
    return YES;
}

- (BOOL)maintainsInactiveSelection
{
    // This method helps to determine whether the WebHTMLView should maintain
    // an inactive selection when it's not first responder.
    // Traditionally, these views have not maintained such selections,
    // clearing them when the view was not first responder. However,
    // to fix bugs like this one:
    // <rdar://problem/3672088>: "Editable WebViews should maintain a selection even 
    //                            when they're not firstResponder"
    // it was decided to add a switch to act more like an NSTextView.

    if ([[self _webView] maintainsInactiveSelection])
        return YES;

    // Predict the case where we are losing first responder status only to
    // gain it back again. Want to keep the selection in that case.
    id nextResponder = [[self window] _newFirstResponderAfterResigning];
    if ([nextResponder isKindOfClass:[NSScrollView class]]) {
        id contentView = [nextResponder contentView];
        if (contentView)
            nextResponder = contentView;
    }
    if ([nextResponder isKindOfClass:[NSClipView class]]) {
        id documentView = [nextResponder documentView];
        if (documentView)
            nextResponder = documentView;
    }
    if (nextResponder == self)
        return YES;

    Frame* coreFrame = core([self _frame]);
    bool selectionIsEditable = coreFrame && coreFrame->selectionController()->isContentEditable();
    bool nextResponderIsInWebView = [nextResponder isKindOfClass:[NSView class]]
        && [nextResponder isDescendantOf:[[[self _webView] mainFrame] frameView]];

    return selectionIsEditable && nextResponderIsInWebView;
}

- (void)addMouseMovedObserver
{
    if (!_private->dataSource || ![self _isTopHTMLView])
        return;

    // Unless the Dashboard asks us to do this for all windows, keep an observer going only for the key window.
    if (!([[self window] isKeyWindow] || [[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows]))
        return;

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mouseMovedNotification:)
        name:WKMouseMovedNotification() object:nil];
    [self _frameOrBoundsChanged];
}

- (void)removeMouseMovedObserverUnconditionally
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
        name:WKMouseMovedNotification() object:nil];
}

- (void)removeMouseMovedObserver
{
    // Don't remove the observer if we're running the Dashboard.
    if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows])
        return;

    [[self _webView] _mouseDidMoveOverElement:nil modifierFlags:0];
    [self removeMouseMovedObserverUnconditionally];
}

- (void)addSuperviewObservers
{
    // We watch the bounds of our superview, so that we can do a layout when the size
    // of the superview changes. This is different from other scrollable things that don't
    // need this kind of thing because their layout doesn't change.
    
    // We need to pay attention to both height and width because our "layout" has to change
    // to extend the background the full height of the space and because some elements have
    // sizes that are based on the total size of the view.
    
    NSView *superview = [self superview];
    if (superview && [self window]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) 
            name:NSViewFrameDidChangeNotification object:superview];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) 
            name:NSViewBoundsDidChangeNotification object:superview];

        // In addition to registering for frame/bounds change notifications, call -_frameOrBoundsChanged.
        // It will check the current size/scroll against the previous layout's size/scroll.  We need to
        // do this here to catch the case where the WebView is laid out at one size, removed from its
        // window, resized, and inserted into another window.  Our frame/bounds changed notifications
        // will not be sent in that situation, since we only watch for changes while in the view hierarchy.
        [self _frameOrBoundsChanged];
    }
}

- (void)removeSuperviewObservers
{
    NSView *superview = [self superview];
    if (superview && [self window]) {
        [[NSNotificationCenter defaultCenter] removeObserver:self
            name:NSViewFrameDidChangeNotification object:superview];
        [[NSNotificationCenter defaultCenter] removeObserver:self
            name:NSViewBoundsDidChangeNotification object:superview];
    }
}

- (void)addWindowObservers
{
    NSWindow *window = [self window];
    if (window) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeKey:)
            name:NSWindowDidBecomeKeyNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResignKey:)
            name:NSWindowDidResignKeyNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:)
            name:NSWindowWillCloseNotification object:window];
    }
}

- (void)removeWindowObservers
{
    NSWindow *window = [self window];
    if (window) {
        [[NSNotificationCenter defaultCenter] removeObserver:self
            name:NSWindowDidBecomeKeyNotification object:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:self
            name:NSWindowDidResignKeyNotification object:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:self
            name:NSWindowWillCloseNotification object:window];
    }
}

- (void)viewWillMoveToSuperview:(NSView *)newSuperview
{
    [self removeSuperviewObservers];
}

- (void)viewDidMoveToSuperview
{
    // Do this here in case the text size multiplier changed when a non-HTML
    // view was installed.
    if ([self superview] != nil) {
        [self _updateTextSizeMultiplier];
        [self addSuperviewObservers];
    }
}

static void _updateActiveStateTimerCallback(CFRunLoopTimerRef timer, void *info)
{
    WebHTMLView *view = (WebHTMLView *)info;
    [view _updateActiveState];
}

- (void)viewWillMoveToWindow:(NSWindow *)window
{
    // Don't do anything if we aren't initialized.  This happens
    // when decoding a WebView.  When WebViews are decoded their subviews
    // are created by initWithCoder: and so won't be normally
    // initialized.  The stub views are discarded by WebView.
    if (!_private)
        return;

    // FIXME: Some of these calls may not work because this view may be already removed from it's superview.
    [self removeMouseMovedObserverUnconditionally];
    [self removeWindowObservers];
    [self removeSuperviewObservers];
    [self _cancelUpdateMouseoverTimer];
    [self _cancelUpdateActiveStateTimer];
    
    [[self _pluginController] stopAllPlugins];
}

- (void)viewDidMoveToWindow
{
    // Don't do anything if we aren't initialized.  This happens
    // when decoding a WebView.  When WebViews are decoded their subviews
    // are created by initWithCoder: and so won't be normally
    // initialized.  The stub views are discarded by WebView.
    if (!_private)
        return;
        
    [self _stopAutoscrollTimer];
    if ([self window]) {
        _private->lastScrollPosition = [[self superview] bounds].origin;
        [self addWindowObservers];
        [self addSuperviewObservers];
        [self addMouseMovedObserver];

        // Schedule this update, rather than making the call right now.
        // The reason is that placing the caret in the just-installed view requires
        // the HTML/XML document to be available on the WebCore side, but it is not
        // at the time this code is running. However, it will be there on the next
        // crank of the run loop. Doing this helps to make a blinking caret appear 
        // in a new, empty window "automatic".
        if (!_private->updateActiveStateTimer) {
            CFRunLoopTimerContext context = { 0, self, NULL, NULL, NULL };
            _private->updateActiveStateTimer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 0, 0, 0,
                                                                    _updateActiveStateTimerCallback, &context);
            CFRunLoopAddTimer(CFRunLoopGetCurrent(), _private->updateActiveStateTimer, kCFRunLoopDefaultMode);
        }
        
        [[self _pluginController] startAllPlugins];

        _private->lastScrollPosition = NSZeroPoint;
    }
}

- (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
{
    [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewWillMoveToHostWindow:) withObject:hostWindow];
}

- (void)viewDidMoveToHostWindow
{
    [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewDidMoveToHostWindow) withObject:nil];
}


- (void)addSubview:(NSView *)view
{
    [super addSubview:view];

    if ([WebPluginController isPlugInView:view])
        [[self _pluginController] addPlugin:view];
}

- (void)willRemoveSubview:(NSView *)subview
{
    if ([WebPluginController isPlugInView:subview])
        [[self _pluginController] destroyPlugin:subview];

    [super willRemoveSubview:subview];
}

- (void)reapplyStyles
{
    if (!_private->needsToApplyStyles) {
        return;
    }
    
#ifdef _KWQ_TIMING        
    double start = CFAbsoluteTimeGetCurrent();
#endif

    [[self _bridge] reapplyStylesForDeviceType:
        _private->printing ? WebCoreDevicePrinter : WebCoreDeviceScreen];
    
#ifdef _KWQ_TIMING        
    double thisTime = CFAbsoluteTimeGetCurrent() - start;
    LOG(Timing, "%s apply style seconds = %f", [self URL], thisTime);
#endif

    _private->needsToApplyStyles = NO;
}

// Do a layout, but set up a new fixed width for the purposes of doing printing layout.
// minPageWidth==0 implies a non-printing layout
- (void)layoutToMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)adjustViewSize
{
    [self reapplyStyles];
    
    if (!_private->needsLayout && ![[self _bridge] needsLayout])
        return;

#ifdef _KWQ_TIMING        
    double start = CFAbsoluteTimeGetCurrent();
#endif

    LOG(View, "%@ doing layout", self);

    if (minPageWidth > 0.0) {
        [[self _bridge] forceLayoutWithMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize];
    } else {
        [[self _bridge] forceLayoutAdjustingViewSize:adjustViewSize];
    }
    _private->needsLayout = NO;
    
    if (!_private->printing) {
        // get size of the containing dynamic scrollview, so
        // appearance and disappearance of scrollbars will not show up
        // as a size change
        NSSize newLayoutFrameSize = [[[self superview] superview] frame].size;
        if (_private->laidOutAtLeastOnce && !NSEqualSizes(_private->lastLayoutFrameSize, newLayoutFrameSize)) {
            [[self _bridge] sendResizeEvent];
            if ([[self _bridge] needsLayout])
                [[self _bridge] forceLayoutAdjustingViewSize:NO];
        }
        _private->laidOutAtLeastOnce = YES;
        _private->lastLayoutSize = [(NSClipView *)[self superview] documentVisibleRect].size;
        _private->lastLayoutFrameSize = newLayoutFrameSize;
    }

#ifdef _KWQ_TIMING        
    double thisTime = CFAbsoluteTimeGetCurrent() - start;
    LOG(Timing, "%s layout seconds = %f", [self URL], thisTime);
#endif
}

- (void)layout
{
    [self layoutToMinimumPageWidth:0.0f maximumPageWidth:0.0f adjustingViewSize:NO];
}

- (NSMenu *)menuForEvent:(NSEvent *)event
{
    [_private->compController endRevertingChange:NO moveLeft:NO];

    _private->handlingMouseDownEvent = YES;
    BOOL handledEvent = NO;
    Frame* coreFrame = core([self _frame]);

    if (!coreFrame) {
        _private->handlingMouseDownEvent = NO;
        return nil;
    }

    Page* page = coreFrame->page();
    if (!page)
        return nil;

    page->contextMenuController()->clearContextMenu();
    handledEvent = coreFrame->eventHandler()->sendContextMenuEvent(PlatformMouseEvent(event));
    _private->handlingMouseDownEvent = NO;

    if (!handledEvent)
        return nil;

    ContextMenu* coreMenu = page->contextMenuController()->contextMenu();
    if (!coreMenu)
        return nil;

    NSArray* menuItems = coreMenu->platformDescription();
    NSMenu* menu = nil;
    if (menuItems && [menuItems count] > 0) {
        menu = [[[NSMenu alloc] init] autorelease];
        for (unsigned i = 0; i < [menuItems count]; i++)
            [menu addItem:[menuItems objectAtIndex:i]];
    }

    return menu;
}

- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag
{
    return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO];
}

- (void)clearFocus
{
    Frame* coreFrame = core([self _frame]);
    if (!coreFrame)
        return;
    Document* document = coreFrame->document();
    if (!document)
        return;
    
    document->setFocusedNode(0);
}

- (BOOL)isOpaque
{
    return [[self _webView] drawsBackground];
}

- (void)setNeedsDisplay:(BOOL)flag
{
    LOG(View, "%@ flag = %d", self, (int)flag);
    [super setNeedsDisplay: flag];
}

- (void)setNeedsLayout: (BOOL)flag
{
    LOG(View, "%@ flag = %d", self, (int)flag);
    _private->needsLayout = flag;
}


- (void)setNeedsToApplyStyles: (BOOL)flag
{
    LOG(View, "%@ flag = %d", self, (int)flag);
    _private->needsToApplyStyles = flag;
}

- (void)drawSingleRect:(NSRect)rect
{
    [NSGraphicsContext saveGraphicsState];
    NSRectClip(rect);
        
    ASSERT([[self superview] isKindOfClass:[WebClipView class]]);

    [(WebClipView *)[self superview] setAdditionalClip:rect];

    NS_DURING {
        if ([self _transparentBackground]) {
            [[NSColor clearColor] set];
            NSRectFill (rect);
        }

        [[self _bridge] drawRect:rect];

        // This hack is needed for <rdar://problem/5023545>. We can hit a race condition where drawRect will be
        // called after the WebView has closed. If the client did not properly close the WebView and set the 
        // UIDelegate to nil, then the UIDelegate will be stale and this code will crash. 
        static BOOL version3OrLaterClient = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_QUICKBOOKS_QUIRK);
        if (version3OrLaterClient) {
            WebView *webView = [self _webView];
            [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView convertRect:rect fromView:self]];
        }

        [(WebClipView *)[self superview] resetAdditionalClip];

        [NSGraphicsContext restoreGraphicsState];
    } NS_HANDLER {
        [(WebClipView *)[self superview] resetAdditionalClip];
        [NSGraphicsContext restoreGraphicsState];
        LOG_ERROR("Exception caught while drawing: %@", localException);
        [localException raise];
    } NS_ENDHANDLER
}

- (void)drawRect:(NSRect)rect
{
    ASSERT_MAIN_THREAD();
    LOG(View, "%@ drawing", self);

    const NSRect *rects;
    NSInteger count;
    [self getRectsBeingDrawn:&rects count:&count];

    BOOL subviewsWereSetAside = _private->subviewsSetAside;
    if (subviewsWereSetAside)
        [self _restoreSubviews];

#ifdef _KWQ_TIMING
    double start = CFAbsoluteTimeGetCurrent();
#endif

    // If count == 0 here, use the rect passed in for drawing. This is a workaround for:
    // <rdar://problem/3908282> REGRESSION (Mail): No drag image dragging selected text in Blot and Mail
    // The reason for the workaround is that this method is called explicitly from the code
    // to generate a drag image, and at that time, getRectsBeingDrawn:count: will return a zero count.
    const int cRectThreshold = 10;
    const float cWastedSpaceThreshold = 0.75f;
    BOOL useUnionedRect = (count <= 1) || (count > cRectThreshold);
    if (!useUnionedRect) {
        // Attempt to guess whether or not we should use the unioned rect or the individual rects.
        // We do this by computing the percentage of "wasted space" in the union.  If that wasted space
        // is too large, then we will do individual rect painting instead.
        float unionPixels = (rect.size.width * rect.size.height);
        float singlePixels = 0;
        for (int i = 0; i < count; ++i)
            singlePixels += rects[i].size.width * rects[i].size.height;
        float wastedSpace = 1 - (singlePixels / unionPixels);
        if (wastedSpace <= cWastedSpaceThreshold)
            useUnionedRect = YES;
    }
    
    if (useUnionedRect)
        [self drawSingleRect:rect];
    else
        for (int i = 0; i < count; ++i)
            [self drawSingleRect:rects[i]];

#ifdef _KWQ_TIMING
    double thisTime = CFAbsoluteTimeGetCurrent() - start;
    LOG(Timing, "%s draw seconds = %f", widget->part()->baseURL().URL().latin1(), thisTime);
#endif

    if (subviewsWereSetAside)
        [self _setAsideSubviews];
}

// Turn off the additional clip while computing our visibleRect.
- (NSRect)visibleRect
{
    if (!([[self superview] isKindOfClass:[WebClipView class]]))
        return [super visibleRect];
        
    WebClipView *clipView = (WebClipView *)[self superview];

    BOOL hasAdditionalClip = [clipView hasAdditionalClip];
    if (!hasAdditionalClip) {
        return [super visibleRect];
    }
    
    NSRect additionalClip = [clipView additionalClip];
    [clipView resetAdditionalClip];
    NSRect visibleRect = [super visibleRect];
    [clipView setAdditionalClip:additionalClip];
    return visibleRect;
}

- (BOOL)isFlipped 
{
    return YES;
}

- (void)windowDidBecomeKey:(NSNotification *)notification
{
    NSWindow *keyWindow = [notification object];

    if (keyWindow == [self window])
        [self addMouseMovedObserver];

    if (keyWindow == [self window] || keyWindow == [[self window] attachedSheet])
        [self _updateActiveState];
}

- (void)windowDidResignKey:(NSNotification *)notification
{
    NSWindow *formerKeyWindow = [notification object];

    if (formerKeyWindow == [self window])
        [self removeMouseMovedObserver];

    if (formerKeyWindow == [self window] || formerKeyWindow == [[self window] attachedSheet]) {
        [self _updateActiveState];
        [_private->compController endRevertingChange:NO moveLeft:NO];
    }
}

- (void)windowWillClose:(NSNotification *)notification
{
    [_private->compController endRevertingChange:NO moveLeft:NO];
    [[self _pluginController] destroyAllPlugins];
}

- (void)scrollWheel:(NSEvent *)event
{
    [self retain];
    Frame* frame = core([self _frame]);
    if (!frame || !frame->eventHandler()->wheelEvent(event))
        [[self nextResponder] scrollWheel:event];    
    [self release];
}

- (BOOL)_isSelectionEvent:(NSEvent *)event
{
    NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
    return [[[self elementAtPoint:point allowShadowContent:YES] objectForKey:WebElementIsSelectedKey] boolValue];
}

- (BOOL)acceptsFirstMouse:(NSEvent *)event
{
    NSView *hitView = [self _hitViewForEvent:event];
    WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil;
    
    if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysAcceptsFirstMouse])
        return YES;
    
    if (hitHTMLView) {
        bool result = false;
        if (Frame* coreFrame = core([hitHTMLView _frame])) {
            coreFrame->eventHandler()->setActivationEventNumber([event eventNumber]);
            [hitHTMLView _setMouseDownEvent:event];
            if ([hitHTMLView _isSelectionEvent:event])
                result = coreFrame->eventHandler()->eventMayStartDrag(event);
            [hitHTMLView _setMouseDownEvent:nil];
        }
        return result;
    }
    return [hitView acceptsFirstMouse:event];
}

- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event
{
    NSView *hitView = [self _hitViewForEvent:event];
    WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil;
    if (hitHTMLView) {
        bool result = false;
        if ([hitHTMLView _isSelectionEvent:event])
            if (Frame* coreFrame = core([hitHTMLView _frame])) {
                [hitHTMLView _setMouseDownEvent:event];
                result = coreFrame->eventHandler()->eventMayStartDrag(event);
                [hitHTMLView _setMouseDownEvent:nil];
            }
        return result;
    }
    return [hitView shouldDelayWindowOrderingForEvent:event];
}

- (void)mouseDown:(NSEvent *)event
{
    [self retain];

    _private->handlingMouseDownEvent = YES;

    // Record the mouse down position so we can determine drag hysteresis.
    [self _setMouseDownEvent:event];

    NSInputManager *currentInputManager = [NSInputManager currentInputManager];
    if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
        goto done;

    [_private->compController endRevertingChange:NO moveLeft:NO];

    // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here.
    // We don't want to pass them along to KHTML a second time.
    if (!([event modifierFlags] & NSControlKeyMask)) {
        _private->ignoringMouseDraggedEvents = NO;

        // Don't do any mouseover while the mouse is down.
        [self _cancelUpdateMouseoverTimer];

        // Let WebCore get a chance to deal with the event. This will call back to us
        // to start the autoscroll timer if appropriate.
        if (Frame* coreframe = core([self _frame]))
            coreframe->eventHandler()->mouseDown(event);
    }

done:
    [_private->firstResponderTextViewAtMouseDownTime release];
    _private->firstResponderTextViewAtMouseDownTime = nil;

    _private->handlingMouseDownEvent = NO;
    
    [self release];
}

- (void)dragImage:(NSImage *)dragImage
               at:(NSPoint)at
           offset:(NSSize)offset
            event:(NSEvent *)event
       pasteboard:(NSPasteboard *)pasteboard
           source:(id)source
        slideBack:(BOOL)slideBack
{
    ASSERT(self == [self _topHTMLView]);
    [super dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack];
}

- (void)mouseDragged:(NSEvent *)event
{
    NSInputManager *currentInputManager = [NSInputManager currentInputManager];
    if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
        return;

    [self retain];

    if (!_private->ignoringMouseDraggedEvents)
        if (Frame* coreframe = core([self _frame]))
            coreframe->eventHandler()->mouseDragged(event);

    [self release];
}

- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
    ASSERT(![self _webView] || [self _isTopHTMLView]);
    
    Page *page = core([self _webView]);
    
    if (!page)
        return NSDragOperationNone;
    
    if (page->dragController()->dragOperation() == DragOperationNone)
        return NSDragOperationGeneric | NSDragOperationCopy;
    
    return (NSDragOperation)page->dragController()->dragOperation();
}

- (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenLoc
{
    ASSERT(![self _webView] || [self _isTopHTMLView]);
    
    NSPoint windowImageLoc = [[self window] convertScreenToBase:screenLoc];
    NSPoint windowMouseLoc = windowImageLoc;
    
    if (Page* page = core([self _webView])) {
        DragController* dragController = page->dragController();
        NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + dragController->dragOffset().x(), windowImageLoc.y + dragController->dragOffset().y());
    }
    
    [[self _bridge] dragSourceMovedTo:windowMouseLoc];
}

- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
{
    ASSERT(![self _webView] || [self _isTopHTMLView]);
    
    NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint];
    NSPoint windowMouseLoc = windowImageLoc;
    
    if (Page* page = core([self _webView])) {
        DragController* dragController = page->dragController();
        windowMouseLoc = NSMakePoint(windowImageLoc.x + dragController->dragOffset().x(), windowImageLoc.y + dragController->dragOffset().y());
        dragController->dragEnded();
    }
    
    [[self _bridge] dragSourceEndedAt:windowMouseLoc operation:operation];
    
    // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event.
    _private->ignoringMouseDraggedEvents = YES;
    
    // Once the dragging machinery kicks in, we no longer get mouse drags or the up event.
    // WebCore expects to get balanced down/up's, so we must fake up a mouseup.
    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
                                            location:windowMouseLoc
                                       modifierFlags:[[NSApp currentEvent] modifierFlags]
                                           timestamp:[NSDate timeIntervalSinceReferenceDate]
                                        windowNumber:[[self window] windowNumber]
                                             context:[[NSApp currentEvent] context]
                                         eventNumber:0 clickCount:0 pressure:0];
    [self mouseUp:fakeEvent]; // This will also update the mouseover state.
}

- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
{
    NSFileWrapper *wrapper = nil;
    
    if (WebCore::CachedResource* tiffResource = [self promisedDragTIFFDataSource]) {
        
        SharedBuffer *buffer = tiffResource->data();
        if (!buffer)
            goto noPromisedData;
        
        NSData *data = buffer->createNSData();
        NSURLResponse *response = tiffResource->response().nsURLResponse();
        
        wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease];
        [wrapper setPreferredFilename:[response suggestedFilename]];
    }
    
noPromisedData:
    
    if (!wrapper) {
        ASSERT(![self _webView] || [self _isTopHTMLView]);
        Page* page = core([self _webView]);
        
        //If a load occurs midway through a drag, the view may be detached, which gives
        //us no ability to get to the original Page, so we cannot access any drag state
        //FIXME: is there a way to recover?
        if (!page) 
            return nil; 
        
        KURL imageURL = page->dragController()->draggingImageURL();
        ASSERT(!imageURL.isEmpty());
        
        wrapper = [[self _dataSource] _fileWrapperForURL:imageURL.getNSURL()];
    }
    
    if (wrapper == nil) {
        LOG_ERROR("Failed to create image file.");
        return nil;
    }

    // FIXME: Report an error if we fail to create a file.
    NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]];
    path = [[NSFileManager defaultManager] _webkit_pathWithUniqueFilenameForPath:path];
    if (![wrapper writeToFile:path atomically:NO updateFilenames:YES])
        LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToFile:atomically:updateFilenames:]");

    return [NSArray arrayWithObject:[path lastPathComponent]];
}

- (void)mouseUp:(NSEvent *)event
{
    [self _setMouseDownEvent:nil];

    NSInputManager *currentInputManager = [NSInputManager currentInputManager];
    if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
        return;

    [self retain];

    [self _stopAutoscrollTimer];
    if (Frame* coreframe = core([self _frame]))
        coreframe->eventHandler()->mouseUp(event);
    [self _updateMouseoverWithFakeEvent];

    [self release];
}

- (void)mouseMovedNotification:(NSNotification *)notification
{
    [self _updateMouseoverWithEvent:[[notification userInfo] objectForKey:@"NSEvent"]];
}

// returning YES from this method is the way we tell AppKit that it is ok for this view
// to be in the key loop even when "tab to all controls" is not on.
- (BOOL)needsPanelToBecomeKey
{
    return YES;
}

- (BOOL)becomeFirstResponder
{
    NSSelectionDirection direction = NSDirectSelection;
    if (![[self _webView] _isPerformingProgrammaticFocus] && !_private->willBecomeFirstResponderForNodeFocus)
        direction = [[self window] keyViewSelectionDirection];
    _private->willBecomeFirstResponderForNodeFocus = NO;

    [self _updateActiveState];
    [self _updateFontPanel];
    
    Frame* frame = core([self _frame]);
    if (!frame)
        return YES;
    
    frame->editor()->setStartNewKillRingSequence(true);

    if (direction == NSDirectSelection)
        return YES;

    Page* page = frame->page();
    if (!page)
        return YES;

    page->focusController()->setFocusedFrame(frame);
    if (Document* document = frame->document())
        document->setFocusedNode(0);
    page->focusController()->setInitialFocus(frame->eventHandler()->currentKeyboardEvent().get());
    return YES;
}

- (BOOL)resignFirstResponder
{
    BOOL resign = [super resignFirstResponder];
    if (resign) {
        [_private->compController endRevertingChange:NO moveLeft:NO];
        _private->resigningFirstResponder = YES;
        if (![self maintainsInactiveSelection]) { 
            [self deselectAll];
            if (![[self _webView] _isPerformingProgrammaticFocus])
                [self clearFocus];
        }
        [self _updateActiveState];
        _private->resigningFirstResponder = NO;
        _private->willBecomeFirstResponderForNodeFocus = NO;
    }
    return resign;
}

- (void)setDataSource:(WebDataSource *)dataSource 
{
    ASSERT(dataSource);
    if (_private->dataSource != dataSource) {
        ASSERT(!_private->dataSource);
        ASSERT(!_private->closed);
        _private->dataSource = [dataSource retain];
        [_private->pluginController setDataSource:dataSource];
        [self addMouseMovedObserver];
    }
}

- (void)dataSourceUpdated:(WebDataSource *)dataSource
{
}

// This is an override of an NSControl method that wants to repaint the entire view when the window resigns/becomes
// key.  WebHTMLView is an NSControl only because it hosts NSCells that are painted by WebCore's Aqua theme
// renderer (and those cells must be hosted by an enclosing NSControl in order to paint properly).
- (void)updateCell:(NSCell*)cell
{
}

// Does setNeedsDisplay:NO as a side effect when printing is ending.
// pageWidth != 0 implies we will relayout to a new width
- (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize
{
    WebFrame *frame = [self _frame];
    NSArray *subframes = [frame childFrames];
    unsigned n = [subframes count];
    unsigned i;
    for (i = 0; i != n; ++i) {
        WebFrame *subframe = [subframes objectAtIndex:i];
        WebFrameView *frameView = [subframe frameView];
        if ([[subframe _dataSource] _isDocumentHTML]) {
            [(WebHTMLView *)[frameView documentView] _setPrinting:printing minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:adjustViewSize];
        }
    }

    if (printing != _private->printing) {
        [_private->pageRects release];
        _private->pageRects = nil;
        _private->printing = printing;
        if (!printing)
            _private->avoidingPrintOrphan = NO;
        [self setNeedsToApplyStyles:YES];
        [self setNeedsLayout:YES];
        [self layoutToMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize];
        if (!printing) {
            // Can't do this when starting printing or nested printing won't work, see 3491427.
            [self setNeedsDisplay:NO];
        }
    }
}

- (BOOL)canPrintHeadersAndFooters
{
    return YES;
}

// This is needed for the case where the webview is embedded in the view that's being printed.
// It shouldn't be called when the webview is being printed directly.
- (void)adjustPageHeightNew:(float *)newBottom top:(float)oldTop bottom:(float)oldBottom limit:(float)bottomLimit
{
    // This helps when we print as part of a larger print process.
    // If the WebHTMLView itself is what we're printing, then we will never have to do this.
    BOOL wasInPrintingMode = _private->printing;
    if (!wasInPrintingMode)
        [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];

    [[self _bridge] adjustPageHeightNew:newBottom top:oldTop bottom:oldBottom limit:bottomLimit];
    
    if (!wasInPrintingMode) {
        NSPrintOperation *currenPrintOperation = [NSPrintOperation currentOperation];
        if (currenPrintOperation)
            // delay _setPrinting:NO until back to main loop as this method may get called repeatedly
            [self performSelector:@selector(_delayedEndPrintMode:) withObject:currenPrintOperation afterDelay:0];
        else
            // not sure if this is actually ever invoked, it probably shouldn't be
            [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];
    }
}

- (float)_availablePaperWidthForPrintOperation:(NSPrintOperation *)printOperation
{
    NSPrintInfo *printInfo = [printOperation printInfo];
    return [printInfo paperSize].width - [printInfo leftMargin] - [printInfo rightMargin];
}

- (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation
{
    float viewWidth = NSWidth([self bounds]);
    if (viewWidth < 1) {
        LOG_ERROR("%@ has no width when printing", self);
        return 1.0f;
    }

    float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
    float maxShrinkToFitScaleFactor = 1.0f / PrintingMaximumShrinkFactor;
    float shrinkToFitScaleFactor = [self _availablePaperWidthForPrintOperation:printOperation]/viewWidth;
    float shrinkToAvoidOrphan = _private->avoidingPrintOrphan ? (1.0f / PrintingOrphanShrinkAdjustment) : 1.0f;
    return userScaleFactor * MAX(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor) * shrinkToAvoidOrphan;
}

// FIXME 3491344: This is a secret AppKit-internal method that we need to override in order
// to get our shrink-to-fit to work with a custom pagination scheme. We can do this better
// if AppKit makes it SPI/API.
- (float)_provideTotalScaleFactorForPrintOperation:(NSPrintOperation *)printOperation 
{
    return [self _scaleFactorForPrintOperation:printOperation];
}

// This is used for Carbon printing. At some point we might want to make this public API.
- (void)setPageWidthForPrinting:(float)pageWidth
{
    [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];
    [self _setPrinting:YES minimumPageWidth:pageWidth maximumPageWidth:pageWidth adjustViewSize:YES];
}

- (void)_endPrintMode
{
    [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES];
    [[self window] setAutodisplay:YES];
}

- (void)_delayedEndPrintMode:(NSPrintOperation *)initiatingOperation
{
    ASSERT_ARG(initiatingOperation, initiatingOperation != nil);
    NSPrintOperation *currentOperation = [NSPrintOperation currentOperation];
    if (initiatingOperation == currentOperation) {
        // The print operation is still underway. We don't expect this to ever happen, hence the assert, but we're
        // being extra paranoid here since the printing code is so fragile. Delay the cleanup
        // further.
        ASSERT_NOT_REACHED();
        [self performSelector:@selector(_delayedEndPrintMode:) withObject:initiatingOperation afterDelay:0];
    } else if ([currentOperation view] == self) {
        // A new print job has started, but it is printing the same WebHTMLView again. We don't expect
        // this to ever happen, hence the assert, but we're being extra paranoid here since the printing code is so
        // fragile. Do nothing, because we don't want to break the print job currently in progress, and
        // the print job currently in progress is responsible for its own cleanup.
        ASSERT_NOT_REACHED();
    } else {
        // The print job that kicked off this delayed call has finished, and this view is not being
        // printed again. We expect that no other print job has started. Since this delayed call wasn't
        // cancelled, beginDocument and endDocument must not have been called, and we need to clean up
        // the print mode here.
        ASSERT(currentOperation == nil);
        [self _endPrintMode];
    }
}

// Return the number of pages available for printing
- (BOOL)knowsPageRange:(NSRangePointer)range
{
    // Must do this explicit display here, because otherwise the view might redisplay while the print
    // sheet was up, using printer fonts (and looking different).
    [self displayIfNeeded];
    [[self window] setAutodisplay:NO];
    
    // If we are a frameset just print with the layout we have onscreen, otherwise relayout
    // according to the paper size
    float minLayoutWidth = 0.0f;
    float maxLayoutWidth = 0.0f;
    Frame* frame = core([self _frame]);
    if (!frame)
        return NO;
    if (!frame->isFrameSet()) {
        float paperWidth = [self _availablePaperWidthForPrintOperation:[NSPrintOperation currentOperation]];
        minLayoutWidth = paperWidth * PrintingMinimumShrinkFactor;
        maxLayoutWidth = paperWidth * PrintingMaximumShrinkFactor;
    }
    [self _setPrinting:YES minimumPageWidth:minLayoutWidth maximumPageWidth:maxLayoutWidth adjustViewSize:YES]; // will relayout
    NSPrintOperation *printOperation = [NSPrintOperation currentOperation];
    // Certain types of errors, including invalid page ranges, can cause beginDocument and
    // endDocument to be skipped after we've put ourselves in print mode (see 4145905). In those cases
    // we need to get out of print mode without relying on any more callbacks from the printing mechanism.
    // If we get as far as beginDocument without trouble, then this delayed request will be cancelled.
    // If not cancelled, this delayed call will be invoked in the next pass through the main event loop,
    // which is after beginDocument and endDocument would be called.
    [self performSelector:@selector(_delayedEndPrintMode:) withObject:printOperation afterDelay:0];
    [[self _webView] _adjustPrintingMarginsForHeaderAndFooter];
    
    // There is a theoretical chance that someone could do some drawing between here and endDocument,
    // if something caused setNeedsDisplay after this point. If so, it's not a big tragedy, because
    // you'd simply see the printer fonts on screen. As of this writing, this does not happen with Safari.

    range->location = 1;
    float totalScaleFactor = [self _scaleFactorForPrintOperation:printOperation];
    float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
    [_private->pageRects release];
    float fullPageHeight = floorf([self _calculatePrintHeight]/totalScaleFactor);
    NSArray *newPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor
                                                                          printHeight:fullPageHeight];
    
    // AppKit gets all messed up if you give it a zero-length page count (see 3576334), so if we
    // hit that case we'll pass along a degenerate 1 pixel square to print. This will print
    // a blank page (with correct-looking header and footer if that option is on), which matches
    // the behavior of IE and Camino at least.
    if ([newPageRects count] == 0)
        newPageRects = [NSArray arrayWithObject:[NSValue valueWithRect:NSMakeRect(0, 0, 1, 1)]];
    else if ([newPageRects count] > 1) {
        // If the last page is a short orphan, try adjusting the print height slightly to see if this will squeeze the
        // content onto one fewer page. If it does, use the adjusted scale. If not, use the original scale.
        float lastPageHeight = NSHeight([[newPageRects lastObject] rectValue]);
        if (lastPageHeight/fullPageHeight < LastPrintedPageOrphanRatio) {
            NSArray *adjustedPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor
                                                                                       printHeight:fullPageHeight*PrintingOrphanShrinkAdjustment];
            // Use the adjusted rects only if the page count went down
            if ([adjustedPageRects count] < [newPageRects count]) {
                newPageRects = adjustedPageRects;
                _private->avoidingPrintOrphan = YES;
            }
        }
    }
    
    _private->pageRects = [newPageRects retain];
    
    range->length = [_private->pageRects count];
    
    return YES;
}

// Return the drawing rectangle for a particular page number
- (NSRect)rectForPage:(int)page
{
    return [[_private->pageRects objectAtIndex:page - 1] rectValue];
}

- (void)drawPageBorderWithSize:(NSSize)borderSize
{
    ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize]));    
    [[self _webView] _drawHeaderAndFooter];
}

- (void)beginDocument
{
    NS_DURING
        // From now on we'll get a chance to call _endPrintMode in either beginDocument or
        // endDocument, so we can cancel the "just in case" pending call.
        [NSObject cancelPreviousPerformRequestsWithTarget:self
                                                 selector:@selector(_delayedEndPrintMode:)
                                                   object:[NSPrintOperation currentOperation]];
        [super beginDocument];
    NS_HANDLER
        // Exception during [super beginDocument] means that endDocument will not get called,
        // so we need to clean up our "print mode" here.
        [self _endPrintMode];
    NS_ENDHANDLER
}

- (void)endDocument
{
    [super endDocument];
    // Note sadly at this point [NSGraphicsContext currentContextDrawingToScreen] is still NO 
    [self _endPrintMode];
}

- (void)keyDown:(NSEvent *)event
{
    RetainPtr<WebHTMLView> selfProtector = self;
    BOOL eventWasSentToWebCore = (_private->keyDownEvent == event);

    BOOL callSuper = NO;

    [_private->keyDownEvent release];
    _private->keyDownEvent = [event retain];

    BOOL completionPopupWasOpen = _private->compController && [_private->compController popupWindowIsOpen];
    if (!eventWasSentToWebCore && core([self _frame])->eventHandler()->keyEvent(event)) {
        // WebCore processed a key event, bail on any preexisting complete: UI
        if (completionPopupWasOpen)
            [_private->compController endRevertingChange:YES moveLeft:NO];
    } else if (!_private->compController || ![_private->compController filterKeyDown:event]) {
        // Not consumed by complete: popup window
        [_private->compController endRevertingChange:YES moveLeft:NO];
        callSuper = YES;
    }
    if (callSuper)
        [super keyDown:event];
    else
        [NSCursor setHiddenUntilMouseMoves:YES];
}

- (void)keyUp:(NSEvent *)event
{
    BOOL eventWasSentToWebCore = (_private->keyDownEvent == event);

    [self retain];
    if (eventWasSentToWebCore || !core([self _frame])->eventHandler()->keyEvent(event))
        [super keyUp:event];    
    [self release];
}

- (id)accessibilityAttributeValue:(NSString*)attributeName
{
    if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) {
        id accTree = [[self _bridge] accessibilityTree];
        if (accTree)
            return [NSArray arrayWithObject:accTree];
        return nil;
    }
    return [super accessibilityAttributeValue:attributeName];
}

- (id)accessibilityFocusedUIElement
{
    id accTree = [[self _bridge] accessibilityTree];
    if (accTree)
        return [accTree accessibilityFocusedUIElement];
    return self;
}

- (id)accessibilityHitTest:(NSPoint)point
{
    id accTree = [[self _bridge] accessibilityTree];
    if (accTree) {
        NSPoint windowCoord = [[self window] convertScreenToBase:point];
        return [accTree accessibilityHitTest:[self convertPoint:windowCoord fromView:nil]];
    }
    return self;
}

- (id)_accessibilityParentForSubview:(NSView *)subview
{
    id accTree = [[self _bridge] accessibilityTree];
    if (!accTree)
        return self;
    id parent = [accTree _accessibilityParentForSubview:subview];
    if (!parent)
        return self;
    return parent;
}

- (void)centerSelectionInVisibleArea:(id)sender
{
    COMMAND_PROLOGUE

    if (Frame* coreFrame = core([self _frame]))
        coreFrame->revealSelection(RenderLayer::gAlignCenterAlways);
}

- (void)pageUp:(id)sender
{
    COMMAND_PROLOGUE

    WebFrameView *frameView = [self _frameView];
    if (!frameView)
        return;
    if ([self _canAlterCurrentSelection])
        [[self _bridge] alterCurrentSelection:SelectionController::MOVE verticalDistance:-[frameView _verticalPageScrollDistance]];
}

- (void)pageDown:(id)sender
{
    COMMAND_PROLOGUE

    WebFrameView *frameView = [self _frameView];
    if (!frameView)
        return;
    if ([self _canAlterCurrentSelection])
        [[self _bridge] alterCurrentSelection:SelectionController::MOVE verticalDistance:[frameView _verticalPageScrollDistance]];
}

- (void)pageUpAndModifySelection:(id)sender
{
    COMMAND_PROLOGUE

    WebFrameView *frameView = [self _frameView];
    if (!frameView)
        return;
    if ([self _canAlterCurrentSelection])
        [[self _bridge] alterCurrentSelection:SelectionController::EXTEND verticalDistance:-[frameView _verticalPageScrollDistance]];
}

- (void)pageDownAndModifySelection:(id)sender
{
    COMMAND_PROLOGUE

    WebFrameView *frameView = [self _frameView];
    if (frameView == nil)
        return;
    if ([self _canAlterCurrentSelection])
        [[self _bridge] alterCurrentSelection:SelectionController::EXTEND verticalDistance:[frameView _verticalPageScrollDistance]];
}

- (void)_expandSelectionToGranularity:(TextGranularity)granularity
{
    if (![self _canAlterCurrentSelection])
        return;
    
    Frame* coreFrame = core([self _frame]);
    if (!coreFrame || !coreFrame->selectionController()->isCaretOrRange())
        return;

    // NOTE: The enums *must* match the very similar ones declared in SelectionController.h
    Selection selection(coreFrame->selectionController()->selection());
    selection.expandUsingGranularity(static_cast<TextGranularity>(granularity));
    
    RefPtr<Range> range = selection.toRange();
    DOMRange *domRange = kit(range.get());
    
    if ([domRange collapsed])
        return;

    EAffinity affinity = coreFrame->selectionController()->affinity();
    WebView *webView = [self _webView];
    if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:domRange affinity:kit(affinity) stillSelecting:NO]) {
        ExceptionCode ec = 0;
        coreFrame->selectionController()->setSelectedRange(range.get(), affinity, true, ec);
    }
}

- (void)selectParagraph:(id)sender
{
    COMMAND_PROLOGUE

    [self _expandSelectionToGranularity:ParagraphGranularity];
}

- (void)selectLine:(id)sender
{
    COMMAND_PROLOGUE

    [self _expandSelectionToGranularity:LineGranularity];
}

- (void)selectSentence:(id)sender
{
    COMMAND_PROLOGUE

    [self _expandSelectionToGranularity:SentenceGranularity];
}

- (void)selectWord:(id)sender
{
    COMMAND_PROLOGUE

    [self _expandSelectionToGranularity:WordGranularity];
}

- (void)delete:(id)sender
{
    COMMAND_PROLOGUE

    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->performDelete();
}

- (NSData *)_selectionStartFontAttributesAsRTF
{
    NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"x"
        attributes:core([self _frame])->fontAttributesForSelectionStart()];
    NSData *data = [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:nil];
    [string release];
    return data;
}

- (NSDictionary *)_fontAttributesFromFontPasteboard
{
    NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
    if (fontPasteboard == nil)
        return nil;
    NSData *data = [fontPasteboard dataForType:NSFontPboardType];
    if (data == nil || [data length] == 0)
        return nil;
    // NSTextView does something more efficient by parsing the attributes only, but that's not available in API.
    NSAttributedString *string = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease];
    if (string == nil || [string length] == 0)
        return nil;
    return [string fontAttributesInRange:NSMakeRange(0, 1)];
}

- (DOMCSSStyleDeclaration *)_emptyStyle
{
    return [[[self _frame] DOMDocument] createCSSStyleDeclaration];
}

- (NSString *)_colorAsString:(NSColor *)color
{
    NSColor *rgbColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
    // FIXME: If color is non-nil and rgbColor is nil, that means we got some kind
    // of fancy color that can't be converted to RGB. Changing that to "transparent"
    // might not be great, but it's probably OK.
    if (rgbColor == nil)
        return @"transparent";
    float r = [rgbColor redComponent];
    float g = [rgbColor greenComponent];
    float b = [rgbColor blueComponent];
    float a = [rgbColor alphaComponent];
    if (a == 0)
        return @"transparent";
    if (r == 0 && g == 0 && b == 0 && a == 1)
        return @"black";
    if (r == 1 && g == 1 && b == 1 && a == 1)
        return @"white";
    // FIXME: Lots more named colors. Maybe we could use the table in WebCore?
    if (a == 1)
        return [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", r * 255, g * 255, b * 255];
    return [NSString stringWithFormat:@"rgba(%.0f,%.0f,%.0f,%f)", r * 255, g * 255, b * 255, a];
}

- (NSString *)_shadowAsString:(NSShadow *)shadow
{
    if (shadow == nil)
        return @"none";
    NSSize offset = [shadow shadowOffset];
    float blurRadius = [shadow shadowBlurRadius];
    if (offset.width == 0 && offset.height == 0 && blurRadius == 0)
        return @"none";
    NSColor *color = [shadow shadowColor];
    if (color == nil)
        return @"none";
    // FIXME: Handle non-integral values here?
    if (blurRadius == 0)
        return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height];
    return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height, blurRadius];
}

- (DOMCSSStyleDeclaration *)_styleFromFontAttributes:(NSDictionary *)dictionary
{
    DOMCSSStyleDeclaration *style = [self _emptyStyle];

    NSColor *color = [dictionary objectForKey:NSBackgroundColorAttributeName];
    [style setBackgroundColor:[self _colorAsString:color]];

    NSFont *font = [dictionary objectForKey:NSFontAttributeName];
    if (font == nil) {
        [style setFontFamily:@"Helvetica"];
        [style setFontSize:@"12px"];
        [style setFontWeight:@"normal"];
        [style setFontStyle:@"normal"];
    } else {
        NSFontManager *fm = [NSFontManager sharedFontManager];
        // FIXME: Need more sophisticated escaping code if we want to handle family names
        // with characters like single quote or backslash in their names.
        [style setFontFamily:[NSString stringWithFormat:@"'%@'", [font familyName]]];
        [style setFontSize:[NSString stringWithFormat:@"%0.fpx", [font pointSize]]];
        if ([fm weightOfFont:font] >= MIN_BOLD_WEIGHT)
            [style setFontWeight:@"bold"];
        else
            [style setFontWeight:@"normal"];
        if (([fm traitsOfFont:font] & NSItalicFontMask) != 0)
            [style setFontStyle:@"italic"];
        else
            [style setFontStyle:@"normal"];
    }

    color = [dictionary objectForKey:NSForegroundColorAttributeName];
    [style setColor:color ? [self _colorAsString:color] : (NSString *)@"black"];

    NSShadow *shadow = [dictionary objectForKey:NSShadowAttributeName];
    [style setTextShadow:[self _shadowAsString:shadow]];

    int strikethroughInt = [[dictionary objectForKey:NSStrikethroughStyleAttributeName] intValue];

    int superscriptInt = [[dictionary objectForKey:NSSuperscriptAttributeName] intValue];
    if (superscriptInt > 0)
        [style setVerticalAlign:@"super"];
    else if (superscriptInt < 0)
        [style setVerticalAlign:@"sub"];
    else
        [style setVerticalAlign:@"baseline"];
    int underlineInt = [[dictionary objectForKey:NSUnderlineStyleAttributeName] intValue];
    // FIXME: Underline wins here if we have both (see bug 3790443).
    if (strikethroughInt == NSUnderlineStyleNone && underlineInt == NSUnderlineStyleNone)
        [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""];
    else if (underlineInt == NSUnderlineStyleNone)
        [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""];
    else
        [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""];

    return style;
}

- (void)_applyStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction
{
    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->applyStyleToSelection(core(style), undoAction);
}

- (void)_applyParagraphStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction
{
    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->applyParagraphStyleToSelection(core(style), undoAction);
}

- (void)_toggleBold
{
    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->execCommand("ToggleBold");
}

- (void)_toggleItalic
{
    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->execCommand("ToggleItalic");
}

- (BOOL)_handleStyleKeyEquivalent:(NSEvent *)event
{
    ASSERT([self _webView]);
    if (![[[self _webView] preferences] respectStandardStyleKeyEquivalents])
        return NO;
    
    if (![self _canEdit])
        return NO;
    
    if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) != NSCommandKeyMask)
        return NO;
    
    NSString *string = [event characters];
    if ([string caseInsensitiveCompare:@"b"] == NSOrderedSame) {
        [self _toggleBold];
        return YES;
    }
    if ([string caseInsensitiveCompare:@"i"] == NSOrderedSame) {
        [self _toggleItalic];
        return YES;
    }
    
    return NO;
}

- (BOOL)performKeyEquivalent:(NSEvent *)event
{
    if ([self _handleStyleKeyEquivalent:event])
        return YES;
    
    BOOL eventWasSentToWebCore = (_private->keyDownEvent == event);
    BOOL ret = NO;

    [_private->keyDownEvent release];
    _private->keyDownEvent = [event retain];
    
    [self retain];

    // Pass command-key combos through WebCore if there is a key binding available for
    // this event. This lets web pages have a crack at intercepting command-modified keypresses.
    // But don't do it if we have already handled the event.
    // Pressing Esc results in a fake event being sent - don't pass it to WebCore.
    if (!eventWasSentToWebCore && event == [NSApp currentEvent] && self == [[self window] firstResponder])
        if (Frame* frame = core([self _frame]))
            ret = frame->eventHandler()->keyEvent(event);

    if (!ret)
        ret = [super performKeyEquivalent:event];

    [self release];
    
    return ret;
}

- (void)copyFont:(id)sender
{
    COMMAND_PROLOGUE

    // Put RTF with font attributes on the pasteboard.
    // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
    NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
    [fontPasteboard declareTypes:[NSArray arrayWithObject:NSFontPboardType] owner:nil];
    [fontPasteboard setData:[self _selectionStartFontAttributesAsRTF] forType:NSFontPboardType];
}

- (void)pasteFont:(id)sender
{
    COMMAND_PROLOGUE

    // Read RTF with font attributes from the pasteboard.
    // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
    [self _applyStyleToSelection:[self _styleFromFontAttributes:[self _fontAttributesFromFontPasteboard]] withUndoAction:EditActionPasteFont];
}

- (void)pasteAsRichText:(id)sender
{
    COMMAND_PROLOGUE

    // Since rich text always beats plain text when both are on the pasteboard, it's not
    // clear how this is different from plain old paste.
    [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO];
}

- (NSFont *)_originalFontA
{
    return [[NSFontManager sharedFontManager] fontWithFamily:@"Helvetica" traits:0 weight:STANDARD_WEIGHT size:10.0f];
}

- (NSFont *)_originalFontB
{
    return [[NSFontManager sharedFontManager] fontWithFamily:@"Times" traits:(NSBoldFontMask | NSItalicFontMask) weight:STANDARD_BOLD_WEIGHT size:12.0f];
}

- (void)_addToStyle:(DOMCSSStyleDeclaration *)style fontA:(NSFont *)a fontB:(NSFont *)b
{
    // Since there's no way to directly ask NSFontManager what style change it's going to do
    // we instead pass two "specimen" fonts to it and let it change them. We then deduce what
    // style change it was doing by looking at what happened to each of the two fonts.
    // So if it was making the text bold, both fonts will be bold after the fact.

    if (a == nil || b == nil)
        return;

    NSFontManager *fm = [NSFontManager sharedFontManager];

    NSFont *oa = [self _originalFontA];

    NSString *aFamilyName = [a familyName];
    NSString *bFamilyName = [b familyName];

    int aPointSize = (int)[a pointSize];
    int bPointSize = (int)[b pointSize];

    int aWeight = [fm weightOfFont:a];
    int bWeight = [fm weightOfFont:b];

    BOOL aIsBold = aWeight >= MIN_BOLD_WEIGHT;

    BOOL aIsItalic = ([fm traitsOfFont:a] & NSItalicFontMask) != 0;
    BOOL bIsItalic = ([fm traitsOfFont:b] & NSItalicFontMask) != 0;

    if ([aFamilyName isEqualToString:bFamilyName]) {
        NSString *familyNameForCSS = aFamilyName;

        // The family name may not be specific enough to get us the font specified.
        // In some cases, the only way to get exactly what we are looking for is to use
        // the Postscript name.
        
        // Find the font the same way the rendering code would later if it encountered this CSS.
        NSFontTraitMask traits = 0;
        if (aIsBold)
            traits |= NSBoldFontMask;
        if (aIsItalic)
            traits |= NSItalicFontMask;
        NSFont *foundFont = WebCoreFindFont(aFamilyName, traits, aPointSize);

        // If we don't find a font with the same Postscript name, then we'll have to use the
        // Postscript name to make the CSS specific enough.
        if (![[foundFont fontName] isEqualToString:[a fontName]]) {
            familyNameForCSS = [a fontName];
        }

        // FIXME: Need more sophisticated escaping code if we want to handle family names
        // with characters like single quote or backslash in their names.
        [style setFontFamily:[NSString stringWithFormat:@"'%@'", familyNameForCSS]];
    }

    int soa = (int)[oa pointSize];
    if (aPointSize == bPointSize)
        [style setFontSize:[NSString stringWithFormat:@"%dpx", aPointSize]];
    else if (aPointSize < soa)
        [style _setFontSizeDelta:@"-1px"];
    else if (aPointSize > soa)
        [style _setFontSizeDelta:@"1px"];

    if (aWeight == bWeight)
        [style setFontWeight:aIsBold ? @"bold" : @"normal"];

    if (aIsItalic == bIsItalic)
        [style setFontStyle:aIsItalic ? @"italic" :  @"normal"];
}

- (DOMCSSStyleDeclaration *)_styleFromFontManagerOperation
{
    DOMCSSStyleDeclaration *style = [self _emptyStyle];

    NSFontManager *fm = [NSFontManager sharedFontManager];

    NSFont *oa = [self _originalFontA];
    NSFont *ob = [self _originalFontB];    
    [self _addToStyle:style fontA:[fm convertFont:oa] fontB:[fm convertFont:ob]];

    return style;
}

- (void)changeFont:(id)sender
{
    COMMAND_PROLOGUE

    [self _applyStyleToSelection:[self _styleFromFontManagerOperation] withUndoAction:EditActionSetFont];
}

- (DOMCSSStyleDeclaration *)_styleForAttributeChange:(id)sender
{
    DOMCSSStyleDeclaration *style = [self _emptyStyle];

    NSShadow *shadow = [[NSShadow alloc] init];
    [shadow setShadowOffset:NSMakeSize(1, 1)];

    NSDictionary *oa = [NSDictionary dictionaryWithObjectsAndKeys:
        [self _originalFontA], NSFontAttributeName,
        nil];
    NSDictionary *ob = [NSDictionary dictionaryWithObjectsAndKeys:
        [NSColor blackColor], NSBackgroundColorAttributeName,
        [self _originalFontB], NSFontAttributeName,
        [NSColor whiteColor], NSForegroundColorAttributeName,
        shadow, NSShadowAttributeName,
        [NSNumber numberWithInt:NSUnderlineStyleSingle], NSStrikethroughStyleAttributeName,
        [NSNumber numberWithInt:1], NSSuperscriptAttributeName,
        [NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName,
        nil];

    [shadow release];

#if 0

NSObliquenessAttributeName        /* float; skew to be applied to glyphs, default 0: no skew */
    // font-style, but that is just an on-off switch

NSExpansionAttributeName          /* float; log of expansion factor to be applied to glyphs, default 0: no expansion */
    // font-stretch?

NSKernAttributeName               /* float, amount to modify default kerning, if 0, kerning off */
    // letter-spacing? probably not good enough

NSUnderlineColorAttributeName     /* NSColor, default nil: same as foreground color */
NSStrikethroughColorAttributeName /* NSColor, default nil: same as foreground color */
    // text-decoration-color?

NSLigatureAttributeName           /* int, default 1: default ligatures, 0: no ligatures, 2: all ligatures */
NSBaselineOffsetAttributeName     /* float, in points; offset from baseline, default 0 */
NSStrokeWidthAttributeName        /* float, in percent of font point size, default 0: no stroke; positive for stroke alone, negative for stroke and fill (a typical value for outlined text would be 3.0) */
NSStrokeColorAttributeName        /* NSColor, default nil: same as foreground color */
    // need extensions?

#endif
    
    NSDictionary *a = [sender convertAttributes:oa];
    NSDictionary *b = [sender convertAttributes:ob];

    NSColor *ca = [a objectForKey:NSBackgroundColorAttributeName];
    NSColor *cb = [b objectForKey:NSBackgroundColorAttributeName];
    if (ca == cb) {
        [style setBackgroundColor:[self _colorAsString:ca]];
    }

    [self _addToStyle:style fontA:[a objectForKey:NSFontAttributeName] fontB:[b objectForKey:NSFontAttributeName]];

    ca = [a objectForKey:NSForegroundColorAttributeName];
    cb = [b objectForKey:NSForegroundColorAttributeName];
    if (ca == cb) {
        [style setColor:[self _colorAsString:ca]];
    }

    NSShadow *sha = [a objectForKey:NSShadowAttributeName];
    if (sha)
        [style setTextShadow:[self _shadowAsString:sha]];
    else if ([b objectForKey:NSShadowAttributeName] == nil)
        [style setTextShadow:@"none"];

    int sa = [[a objectForKey:NSStrikethroughStyleAttributeName] intValue];
    int sb = [[b objectForKey:NSStrikethroughStyleAttributeName] intValue];
    if (sa == sb) {
        if (sa == NSUnderlineStyleNone)
            [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; 
            // we really mean "no line-through" rather than "none"
        else
            [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""];
            // we really mean "add line-through" rather than "line-through"
    }

    sa = [[a objectForKey:NSSuperscriptAttributeName] intValue];
    sb = [[b objectForKey:NSSuperscriptAttributeName] intValue];
    if (sa == sb) {
        if (sa > 0)
            [style setVerticalAlign:@"super"];
        else if (sa < 0)
            [style setVerticalAlign:@"sub"];
        else
            [style setVerticalAlign:@"baseline"];
    }

    int ua = [[a objectForKey:NSUnderlineStyleAttributeName] intValue];
    int ub = [[b objectForKey:NSUnderlineStyleAttributeName] intValue];
    if (ua == ub) {
        if (ua == NSUnderlineStyleNone)
            [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""];
            // we really mean "no underline" rather than "none"
        else
            [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""];
            // we really mean "add underline" rather than "underline"
    }

    return style;
}

- (void)changeAttributes:(id)sender
{
    COMMAND_PROLOGUE

    [self _applyStyleToSelection:[self _styleForAttributeChange:sender] withUndoAction:EditActionChangeAttributes];
}

- (DOMCSSStyleDeclaration *)_styleFromColorPanelWithSelector:(SEL)selector
{
    DOMCSSStyleDeclaration *style = [self _emptyStyle];

    ASSERT([style respondsToSelector:selector]);
    [style performSelector:selector withObject:[self _colorAsString:[[NSColorPanel sharedColorPanel] color]]];
    
    return style;
}

- (EditAction)_undoActionFromColorPanelWithSelector:(SEL)selector
{
    if (selector == @selector(setBackgroundColor:))
        return EditActionSetBackgroundColor;    
    return EditActionSetColor;
}

- (void)_changeCSSColorUsingSelector:(SEL)selector inRange:(DOMRange *)range
{
    DOMCSSStyleDeclaration *style = [self _styleFromColorPanelWithSelector:selector];
    WebView *webView = [self _webView];
    if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:range])
        core([self _frame])->editor()->applyStyle(core(style), [self _undoActionFromColorPanelWithSelector:selector]);
}

- (void)changeDocumentBackgroundColor:(id)sender
{
    COMMAND_PROLOGUE

    // Mimicking NSTextView, this method sets the background color for the
    // entire document. There is no NSTextView API for setting the background
    // color on the selected range only. Note that this method is currently
    // never called from the UI (see comment in changeColor:).
    // FIXME: this actually has no effect when called, probably due to 3654850. _documentRange seems
    // to do the right thing because it works in startSpeaking:, and I know setBackgroundColor: does the
    // right thing because I tested it with [self _selectedRange].
    // FIXME: This won't actually apply the style to the entire range here, because it ends up calling
    // [bridge applyStyle:], which operates on the current selection. To make this work right, we'll
    // need to save off the selection, temporarily set it to the entire range, make the change, then
    // restore the old selection.
    [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _documentRange]];
}

- (void)changeColor:(id)sender
{
    COMMAND_PROLOGUE

    // FIXME: in NSTextView, this method calls changeDocumentBackgroundColor: when a
    // private call has earlier been made by [NSFontFontEffectsBox changeColor:], see 3674493. 
    // AppKit will have to be revised to allow this to work with anything that isn't an 
    // NSTextView. However, this might not be required for Tiger, since the background-color 
    // changing box in the font panel doesn't work in Mail (3674481), though it does in TextEdit.
    [self _applyStyleToSelection:[self _styleFromColorPanelWithSelector:@selector(setColor:)] 
                  withUndoAction:EditActionSetColor];
}

- (void)_alignSelectionUsingCSSValue:(NSString *)CSSAlignmentValue withUndoAction:(EditAction)undoAction
{
    if (![self _canEditRichly])
        return;
        
    DOMCSSStyleDeclaration *style = [self _emptyStyle];
    [style setTextAlign:CSSAlignmentValue];
    [self _applyStyleToSelection:style withUndoAction:undoAction];
}

- (void)alignCenter:(id)sender
{
    COMMAND_PROLOGUE

    [self _alignSelectionUsingCSSValue:@"center" withUndoAction:EditActionCenter];
}

- (void)alignJustified:(id)sender
{
    COMMAND_PROLOGUE

    [self _alignSelectionUsingCSSValue:@"justify" withUndoAction:EditActionJustify];
}

- (void)alignLeft:(id)sender
{
    COMMAND_PROLOGUE

    [self _alignSelectionUsingCSSValue:@"left" withUndoAction:EditActionAlignLeft];
}

- (void)alignRight:(id)sender
{
    COMMAND_PROLOGUE

    [self _alignSelectionUsingCSSValue:@"right" withUndoAction:EditActionAlignRight];
}

- (void)insertParagraphSeparator:(id)sender
{
    COMMAND_PROLOGUE

    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->execCommand("InsertNewline");
}

- (void)_changeWordCaseWithSelector:(SEL)selector
{
    if (![self _canEdit])
        return;

    WebFrameBridge *bridge = [self _bridge];
    [self selectWord:nil];
    NSString *word = [[bridge selectedString] performSelector:selector];
    // FIXME: Does this need a different action context other than "typed"?
    if ([self _shouldReplaceSelectionWithText:word givenAction:WebViewInsertActionTyped])
        [bridge replaceSelectionWithText:word selectReplacement:NO smartReplace:NO];
}

- (void)uppercaseWord:(id)sender
{
    COMMAND_PROLOGUE

    [self _changeWordCaseWithSelector:@selector(uppercaseString)];
}

- (void)lowercaseWord:(id)sender
{
    COMMAND_PROLOGUE

    [self _changeWordCaseWithSelector:@selector(lowercaseString)];
}

- (void)capitalizeWord:(id)sender
{
    COMMAND_PROLOGUE

    [self _changeWordCaseWithSelector:@selector(capitalizedString)];
}

- (void)deleteForward:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _isEditable])
        return;
    Frame* coreFrame = core([self _frame]);
    if (coreFrame)
        coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, CharacterGranularity, false, true);
}

- (void)deleteBackward:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _isEditable])
        return;
    Frame* coreFrame = core([self _frame]);
    if (coreFrame)
        coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, CharacterGranularity, false, true);
}

- (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender
{
    COMMAND_PROLOGUE

    LOG_ERROR("unimplemented, doing deleteBackward instead");

    if (![self _isEditable])
        return;
    Frame* coreFrame = core([self _frame]);
    if (coreFrame)
        coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, CharacterGranularity, false, true);
}

- (void)deleteToBeginningOfLine:(id)sender
{
    COMMAND_PROLOGUE

    Frame* coreFrame = core([self _frame]);
    if (coreFrame)
        coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, LineBoundary, true, false);
}

- (void)deleteToEndOfLine:(id)sender
{
    COMMAND_PROLOGUE

    // To match NSTextView, this command should delete the newline at the end of
    // a paragraph if you are at the end of a paragraph (like deleteToEndOfParagraph does below).
    Frame* coreFrame = core([self _frame]);
    if (coreFrame) {
        if (!coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, LineBoundary, true, false))
            coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, CharacterGranularity, true, false);
    }
    
}

- (void)deleteToBeginningOfParagraph:(id)sender
{
    COMMAND_PROLOGUE

    Frame* coreFrame = core([self _frame]);
    if (coreFrame)
        coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, ParagraphBoundary, true, false);
}

- (void)deleteToEndOfParagraph:(id)sender
{
    COMMAND_PROLOGUE

    // Despite the name of the method, this should delete the newline if the caret is at the end of a paragraph.
    // If deletion to the end of the paragraph fails, we delete one character forward, which will delete the newline.
    Frame* coreFrame = core([self _frame]);
    if (coreFrame) {
        if (!coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, ParagraphBoundary, true, false))
            coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, CharacterGranularity, true, false);
    }
}

- (void)complete:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _canEdit])
        return;
    if (!_private->compController)
        _private->compController = [[WebTextCompleteController alloc] initWithHTMLView:self];
    [_private->compController doCompletion];
}

- (void)checkSpelling:(id)sender
{
    COMMAND_PROLOGUE

    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
    if (!checker) {
        LOG_ERROR("No NSSpellChecker");
        return;
    }
    
    core([self _frame])->editor()->advanceToNextMisspelling();
}

- (void)showGuessPanel:(id)sender
{
    COMMAND_PROLOGUE

    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
    if (!checker) {
        LOG_ERROR("No NSSpellChecker");
        return;
    }
    
    NSPanel *spellingPanel = [checker spellingPanel];
#ifndef BUILDING_ON_TIGER
    // Post-Tiger, this menu item is a show/hide toggle, to match AppKit. Leave Tiger behavior alone
    // to match rest of OS X.
    if ([spellingPanel isVisible]) {
        [spellingPanel orderOut:sender];
        return;
    }
#endif
    
    core([self _frame])->editor()->advanceToNextMisspelling(true);
    [spellingPanel orderFront:sender];
}

- (void)_changeSpellingToWord:(NSString *)newWord
{
    if (![self _canEdit])
        return;

    // Don't correct to empty string.  (AppKit checked this, we might as well too.)
    if (![NSSpellChecker sharedSpellChecker]) {
        LOG_ERROR("No NSSpellChecker");
        return;
    }
    
    if ([newWord isEqualToString:@""])
        return;

    if ([self _shouldReplaceSelectionWithText:newWord givenAction:WebViewInsertActionPasted])
        [[self _bridge] replaceSelectionWithText:newWord selectReplacement:YES smartReplace:NO];
}

- (void)changeSpelling:(id)sender
{
    COMMAND_PROLOGUE

    [self _changeSpellingToWord:[[sender selectedCell] stringValue]];
}

- (void)ignoreSpelling:(id)sender
{
    COMMAND_PROLOGUE

    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
    if (!checker) {
        LOG_ERROR("No NSSpellChecker");
        return;
    }
    
    NSString *stringToIgnore = [sender stringValue];
    unsigned int length = [stringToIgnore length];
    if (stringToIgnore && length > 0) {
        [checker ignoreWord:stringToIgnore inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]];
        // FIXME: Need to clear misspelling marker if the currently selected word is the one we are to ignore?
    }
}

- (void)performFindPanelAction:(id)sender
{
    COMMAND_PROLOGUE

    // Implementing this will probably require copying all of NSFindPanel.h and .m.
    // We need *almost* the same thing as AppKit, but not quite.
    LOG_ERROR("unimplemented");
}

- (void)startSpeaking:(id)sender
{
    COMMAND_PROLOGUE

    WebFrameBridge *bridge = [self _bridge];
    DOMRange *range = [self _selectedRange];
    if (!range || [range collapsed])
        range = [self _documentRange];
    [NSApp speakString:[bridge stringForRange:range]];
}

- (void)stopSpeaking:(id)sender
{
    COMMAND_PROLOGUE

    [NSApp stopSpeaking:sender];
}

- (void)insertNewlineIgnoringFieldEditor:(id)sender
{
    COMMAND_PROLOGUE

    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->execCommand("InsertNewline");
}

- (void)insertTabIgnoringFieldEditor:(id)sender
{
    COMMAND_PROLOGUE

    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->execCommand("InsertTab");
}

- (void)subscript:(id)sender
{
    COMMAND_PROLOGUE

    DOMCSSStyleDeclaration *style = [self _emptyStyle];
    [style setVerticalAlign:@"sub"];
    [self _applyStyleToSelection:style withUndoAction:EditActionSubscript];
}

- (void)superscript:(id)sender
{
    COMMAND_PROLOGUE

    DOMCSSStyleDeclaration *style = [self _emptyStyle];
    [style setVerticalAlign:@"super"];
    [self _applyStyleToSelection:style withUndoAction:EditActionSuperscript];
}

- (void)unscript:(id)sender
{
    COMMAND_PROLOGUE

    DOMCSSStyleDeclaration *style = [self _emptyStyle];
    [style setVerticalAlign:@"baseline"];
    [self _applyStyleToSelection:style withUndoAction:EditActionUnscript];
}

- (void)underline:(id)sender
{
    COMMAND_PROLOGUE

    Frame* coreFrame = core([self _frame]);
    if (!coreFrame)
        return;
    // Despite the name, this method is actually supposed to toggle underline.
    // FIXME: This currently clears overline, line-through, and blink as an unwanted side effect.
    DOMCSSStyleDeclaration *style = [self _emptyStyle];
    [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""];
    if (coreFrame->editor()->selectionStartHasStyle(core(style)))
        [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""];
    [self _applyStyleToSelection:style withUndoAction:EditActionUnderline];
}

- (void)yank:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _canEdit])
        return;
        
    NSString* yankee = _NSYankFromKillRing();

    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->insertTextWithoutSendingTextEvent(yankee, false);

    _NSSetKillRingToYankedState();
}

- (void)yankAndSelect:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _canEdit])
        return;

    NSString* yankee = _NSYankFromKillRing();
    
    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->insertTextWithoutSendingTextEvent(yankee, true);
        
    _NSSetKillRingToYankedState();
}

- (void)setMark:(id)sender
{
    COMMAND_PROLOGUE

    [[self _bridge] setMarkDOMRange:[self _selectedRange]];
}

static DOMRange *unionDOMRanges(DOMRange *a, DOMRange *b)
{
    ASSERT(a);
    ASSERT(b);
    DOMRange *s = [a compareBoundaryPoints:DOM_START_TO_START sourceRange:b] <= 0 ? a : b;
    DOMRange *e = [a compareBoundaryPoints:DOM_END_TO_END sourceRange:b] <= 0 ? b : a;
    DOMRange *r = [[[a startContainer] ownerDocument] createRange];
    [r setStart:[s startContainer] offset:[s startOffset]];
    [r setEnd:[e endContainer] offset:[e endOffset]];
    return r;
}

- (void)deleteToMark:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _canEdit])
        return;

    DOMRange *mark = [[self _bridge] markDOMRange];
    if (mark == nil) {
        if (Frame* coreFrame = core([self _frame]))
            coreFrame->editor()->performDelete();
    } else {
        DOMRange *selection = [self _selectedRange];
        DOMRange *r;
        NS_DURING
            r = unionDOMRanges(mark, selection);
        NS_HANDLER
            r = selection;
        NS_ENDHANDLER
        Frame* coreFrame = core([self _frame]);
        if (coreFrame)
            coreFrame->editor()->deleteRange([r _range], true, true, false, deleteSelectionAction, CharacterGranularity);

    }
    [[self _bridge] setMarkDOMRange:[self _selectedRange]];
}

- (void)selectToMark:(id)sender
{
    COMMAND_PROLOGUE

    WebFrameBridge *bridge = [self _bridge];
    DOMRange *mark = [bridge markDOMRange];
    DOMRange *selection = [self _selectedRange];
    Frame* coreFrame = core([self _frame]);
    if (!mark || !selection || !coreFrame) {
        NSBeep();
        return;
    }
    ExceptionCode ec = 0;
    coreFrame->selectionController()->setSelectedRange(core(unionDOMRanges(mark, [self _selectedRange])), DOWNSTREAM, true, ec);
}

- (void)swapWithMark:(id)sender
{
    COMMAND_PROLOGUE

    WebFrameBridge *bridge = [self _bridge];
    DOMRange *mark = [bridge markDOMRange];
    DOMRange *selection = [self _selectedRange];
    Frame* coreFrame = core([self _frame]);
    if (!mark || !selection || !coreFrame) {
        NSBeep();
        return;
    }

    ExceptionCode ec = 0;
    coreFrame->selectionController()->setSelectedRange(core(mark), DOWNSTREAM, true, ec);
    if (ec == 0)
        [bridge setMarkDOMRange:selection];
}

- (void)transpose:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _canEdit])
        return;

    WebFrameBridge *bridge = [self _bridge];
    DOMRange *r = [bridge rangeOfCharactersAroundCaret];
    if (!r)
        return;
    NSString *characters = [bridge stringForRange:r];
    if ([characters length] != 2)
        return;
    NSString *transposed = [[characters substringFromIndex:1] stringByAppendingString:[characters substringToIndex:1]];
    WebView *webView = [self _webView];
    if (![[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange]
            toDOMRange:r affinity:NSSelectionAffinityDownstream stillSelecting:NO])
        return;

    Frame* coreFrame = core([self _frame]);
    if (!coreFrame)
        return;

    ExceptionCode ec = 0;
    coreFrame->selectionController()->setSelectedRange(core(r), DOWNSTREAM, true, ec);
    if ([self _shouldReplaceSelectionWithText:transposed givenAction:WebViewInsertActionTyped])
        [bridge replaceSelectionWithText:transposed selectReplacement:NO smartReplace:NO];
}

- (void)toggleBaseWritingDirection:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _canEdit])
        return;
    
    NSString *direction = @"RTL";
    switch ([[self _bridge] baseWritingDirectionForSelectionStart]) {
        case NSWritingDirectionLeftToRight:
            break;
        case NSWritingDirectionRightToLeft:
            direction = @"LTR";
            break;
        // The writingDirectionForSelectionStart method will never return "natural". It
        // will always return a concrete direction. So, keep the compiler happy, and assert not reached.
        case NSWritingDirectionNatural:
            ASSERT_NOT_REACHED();
            break;
    }

    DOMCSSStyleDeclaration *style = [self _emptyStyle];
    [style setDirection:direction];
    [self _applyParagraphStyleToSelection:style withUndoAction:EditActionSetWritingDirection];
}

- (void)changeBaseWritingDirection:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _canEdit])
        return;
    
    NSWritingDirection writingDirection = static_cast<NSWritingDirection>([sender tag]);
    
    // We disable the menu item that performs this action because we can't implement
    // NSWritingDirectionNatural's behavior using CSS.
    ASSERT(writingDirection != NSWritingDirectionNatural);

    DOMCSSStyleDeclaration *style = [self _emptyStyle];
    [style setDirection:writingDirection == NSWritingDirectionLeftToRight ? @"LTR" : @"RTL"];
    [self _applyParagraphStyleToSelection:style withUndoAction:EditActionSetWritingDirection];
}

- (void)indent:(id)sender
{
    COMMAND_PROLOGUE

    core([self _frame])->editor()->indent();
}

- (void)outdent:(id)sender
{
    COMMAND_PROLOGUE

    core([self _frame])->editor()->outdent();
}

#if 0

// CSS does not have a way to specify an outline font, which may make this difficult to implement.
// Maybe a special case of text-shadow?
- (void)outline:(id)sender;

// This is part of table support, which may be in NSTextView for Tiger.
// It's probably simple to do the equivalent thing for WebKit.
- (void)insertTable:(id)sender;

// This could be important.
- (void)toggleTraditionalCharacterShape:(id)sender;

// I'm not sure what the equivalents of these in the web world are.
- (void)insertLineSeparator:(id)sender;
- (void)insertPageBreak:(id)sender;

// These methods are not implemented in NSTextView yet at the time of this writing.
- (void)changeCaseOfLetter:(id)sender;
- (void)transposeWords:(id)sender;

#endif

// Super-hack alert.
// Workaround for bug 3789278.

// Returns a selector only if called while:
//   1) first responder is self
//   2) handling a key down event
//   3) not yet inside keyDown: method
//   4) key is an arrow key
// The selector is the one that gets sent by -[NSWindow _processKeyboardUIKey] for this key.
- (SEL)_arrowKeyDownEventSelectorIfPreprocessing
{
    NSWindow *w = [self window];
    if ([w firstResponder] != self)
        return NULL;
    NSEvent *e = [w currentEvent];
    if ([e type] != NSKeyDown)
        return NULL;
    if (e == _private->keyDownEvent)
        return NULL;
    NSString *s = [e charactersIgnoringModifiers];
    if ([s length] == 0)
        return NULL;
    switch ([s characterAtIndex:0]) {
        case NSDownArrowFunctionKey:
            return @selector(moveDown:);
        case NSLeftArrowFunctionKey:
            return @selector(moveLeft:);
        case NSRightArrowFunctionKey:
            return @selector(moveRight:);
        case NSUpArrowFunctionKey:
            return @selector(moveUp:);
        default:
            return NULL;
    }
}

// Returns NO instead of YES if called on the selector that the
// _arrowKeyDownEventSelectorIfPreprocessing method returns.
// This should only happen inside -[NSWindow _processKeyboardUIKey],
// and together with the change below should cause that method
// to return NO rather than handling the key.
// Also set a 1-shot flag for the nextResponder check below.
- (BOOL)respondsToSelector:(SEL)selector
{
    if (![super respondsToSelector:selector])
        return NO;
    SEL arrowKeySelector = [self _arrowKeyDownEventSelectorIfPreprocessing];
    if (selector != arrowKeySelector)
        return YES;
    _private->nextResponderDisabledOnce = YES;
    return NO;
}

// Returns nil instead of the next responder if called when the
// one-shot flag is set, and _arrowKeyDownEventSelectorIfPreprocessing
// returns something other than NULL. This should only happen inside
// -[NSWindow _processKeyboardUIKey] and together with the change above
// should cause that method to return NO rather than handling the key.
- (NSResponder *)nextResponder
{
    BOOL disabled = _private->nextResponderDisabledOnce;
    _private->nextResponderDisabledOnce = NO;
    if (disabled && [self _arrowKeyDownEventSelectorIfPreprocessing] != NULL)
        return nil;
    return [super nextResponder];
}

// Despite its name, this is called at different times than windowDidBecomeKey is.
// It takes into account all the other factors that determine when NSCell draws
// with different tints, so it's the right call to use for control tints. We'd prefer
// to do this with API. <rdar://problem/5136760>
- (void)_windowChangedKeyState
{
    if (Frame* frame = core([self _frame]))
        if (FrameView* view = frame->view())
            view->updateControlTints();
    [super _windowChangedKeyState];
}

@end

@implementation WebHTMLView (WebTextSizing)

- (IBAction)_makeTextSmaller:(id)sender
{
    [self _updateTextSizeMultiplier];
}

- (IBAction)_makeTextLarger:(id)sender
{
    [self _updateTextSizeMultiplier];
}

- (IBAction)_makeTextStandardSize:(id)sender
{
    [self _updateTextSizeMultiplier];
}

- (BOOL)_tracksCommonSizeFactor
{
    return YES;
}

- (void)_textSizeMultiplierChanged
{
    [self _updateTextSizeMultiplier];
}

// never sent because we track the common size factor
- (BOOL)_canMakeTextSmaller
{
    ASSERT_NOT_REACHED();
    return NO;
}

- (BOOL)_canMakeTextLarger
{
    ASSERT_NOT_REACHED();
    return NO;
}

- (BOOL)_canMakeTextStandardSize
{
    ASSERT_NOT_REACHED();
    return NO;
}

@end

@implementation NSArray (WebHTMLView)

- (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object
{
#ifndef __LP64__
    NSEnumerator *enumerator = [self objectEnumerator];
    WebNetscapePluginEmbeddedView *view;
    while ((view = [enumerator nextObject]) != nil)
        if ([view isKindOfClass:[WebNetscapePluginEmbeddedView class]])
            [view performSelector:selector withObject:object];
#endif
}

@end

@implementation WebHTMLView (WebInternal)

- (void)_selectionChanged
{
    [self _updateSelectionForInputManager];
    [self _updateFontPanel];
    if (core([self _frame]))
        core([self _frame])->editor()->setStartNewKillRingSequence(true);
}

- (void)_updateFontPanel
{
    // FIXME: NSTextView bails out if becoming or resigning first responder, for which it has ivar flags. Not
    // sure if we need to do something similar.
    
    if (![self _canEdit])
        return;
    
    NSWindow *window = [self window];
    // FIXME: is this first-responder check correct? What happens if a subframe is editable and is first responder?
    if ([NSApp keyWindow] != window || [window firstResponder] != self)
        return;
    
    BOOL multiple = NO;
    NSFont *font = [[self _bridge] fontForSelection:&multiple];

    // FIXME: for now, return a bogus font that distinguishes the empty selection from the non-empty
    // selection. We should be able to remove this once the rest of this code works properly.
    if (font == nil)
        font = [self _hasSelection] ? [NSFont menuFontOfSize:23] : [NSFont toolTipsFontOfSize:17];
    ASSERT(font != nil);

    NSFontManager *fm = [NSFontManager sharedFontManager];
    [fm setSelectedFont:font isMultiple:multiple];

    // FIXME: we don't keep track of selected attributes, or set them on the font panel. This
    // appears to have no effect on the UI. E.g., underlined text in Mail or TextEdit is
    // not reflected in the font panel. Maybe someday this will change.
}

- (BOOL)_canSmartCopyOrDelete
{
    return [[self _webView] smartInsertDeleteEnabled] && [[self _bridge] selectionGranularity] == WordGranularity;
}

- (DOMRange *)_smartDeleteRangeForProposedRange:(DOMRange *)proposedRange
{
    if (proposedRange == nil || [self _canSmartCopyOrDelete] == NO)
        return nil;
    
    return [[self _bridge] smartDeleteRangeForProposedRange:proposedRange];
}

- (void)_smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)rangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString
{
    if (!pasteString || !rangeToReplace || ![[self _webView] smartInsertDeleteEnabled]) {
        if (beforeString)
            *beforeString = nil;
        if (afterString)
            *afterString = nil;
        return;
    }
    
    [[self _bridge] smartInsertForString:pasteString replacingRange:rangeToReplace beforeString:beforeString afterString:afterString];
}

- (BOOL)_textViewWasFirstResponderAtMouseDownTime:(NSTextView *)textView
{
    return textView == _private->firstResponderTextViewAtMouseDownTime;
}


- (NSEvent *)_mouseDownEvent
{
    return _private->mouseDownEvent;
}

#ifndef __LP64__
- (void)_pauseNullEventsForAllNetscapePlugins
{
    NSArray *subviews = [self subviews];
    unsigned int subviewCount = [subviews count];
    unsigned int subviewIndex;
    
    for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) {
        NSView *subview = [subviews objectAtIndex:subviewIndex];
        if ([subview isKindOfClass:[WebBaseNetscapePluginView class]])
            [(WebBaseNetscapePluginView *)subview stopNullEvents];
    }
}
#endif

#ifndef __LP64__
- (void)_resumeNullEventsForAllNetscapePlugins
{
    NSArray *subviews = [self subviews];
    unsigned int subviewCount = [subviews count];
    unsigned int subviewIndex;
    
    for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) {
        NSView *subview = [subviews objectAtIndex:subviewIndex];
        if ([subview isKindOfClass:[WebBaseNetscapePluginView class]])
            [(WebBaseNetscapePluginView *)subview restartNullEvents];
    }
}
#endif

- (void)_willMakeFirstResponderForNodeFocus
{
    _private->willBecomeFirstResponderForNodeFocus = YES;
}

- (id<WebHTMLHighlighter>)_highlighterForType:(NSString*)type
{
    return [_private->highlighters objectForKey:type];
}

- (WebFrame *)_frame
{
    return [_private->dataSource webFrame];
}

- (void)copy:(id)sender
{
    COMMAND_PROLOGUE

    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->copy();
}

- (void)cut:(id)sender
{
    COMMAND_PROLOGUE

    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->cut();
}

- (void)paste:(id)sender
{
    COMMAND_PROLOGUE

    Frame* coreFrame = core([self _frame]);
    if (coreFrame && coreFrame->editor()->tryDHTMLPaste())
        return; // DHTML did the whole operation
    if (!coreFrame->editor()->canPaste())
        return;
    if (coreFrame && coreFrame->selectionController()->isContentRichlyEditable())
        [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:YES];
    else
        coreFrame->editor()->pasteAsPlainText();
}

- (void)pasteAsPlainText:(id)sender
{
    COMMAND_PROLOGUE

    if (![self _canEdit])
        return;
    [self _pasteAsPlainTextWithPasteboard:[NSPasteboard generalPasteboard]];
}

- (void)closeIfNotCurrentView
{
    if ([[[self _frame] frameView] documentView] != self)
        [self close];
}

- (DOMDocumentFragment*)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard
{
    BOOL discard;
    return [self _documentFragmentFromPasteboard:pasteboard inContext:nil allowPlainText:NO chosePlainText:&discard];
}

#ifndef BUILDING_ON_TIGER

- (BOOL)isGrammarCheckingEnabled
{
    // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because
    // the AppKit code checks the first responder.
    return [[self _webView] isGrammarCheckingEnabled];
}

- (void)setGrammarCheckingEnabled:(BOOL)flag
{
    // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because
    // the AppKit code checks the first responder.
    [[self _webView] setGrammarCheckingEnabled:flag];
}

- (void)toggleGrammarChecking:(id)sender
{
    // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because
    // the AppKit code checks the first responder.
    [[self _webView] toggleGrammarChecking:sender];
}


static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
{
    NSArray *screens = [NSScreen screens];
    
    if ([screens count] == 0) {
        // You could theoretically get here if running with no monitor, in which case it doesn't matter
        // much where the "on-screen" point is.
        return CGPointMake(point.x, point.y);
    }
    
    // Flip the y coordinate from the top of the menu bar screen -- see 4636390
    return CGPointMake(point.x, NSMaxY([[screens objectAtIndex:0] frame]) - point.y);
}

#endif

- (void)_lookUpInDictionaryFromMenu:(id)sender
{
    // Dictionary API will accept a whitespace-only string and display UI as if it were real text,
    // so bail out early to avoid that.
    if ([[[self selectedString] _webkit_stringByTrimmingWhitespace] length] == 0)
        return;

    // We soft link to get the function that displays the dictionary (either pop-up window or app) to avoid the performance
    // penalty of linking to another framework. This function changed signature as well as framework between Tiger and Leopard,
    // so the two cases are handled separately.

#ifdef BUILDING_ON_TIGER
    typedef OSStatus (*ServiceWindowShowFunction)(id inWordString, NSRect inWordBoundary, UInt16 inLineDirection);
    const char *frameworkPath = "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/LangAnalysis.framework/LangAnalysis";
    const char *functionName = "DCMDictionaryServiceWindowShow";
#else
    typedef void (*ServiceWindowShowFunction)(id unusedDictionaryRef, id inWordString, CFRange selectionRange, id unusedFont, CGPoint textOrigin, Boolean verticalText, id unusedTransform);
    const char *frameworkPath = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox";
    const char *functionName = "HIDictionaryWindowShow";
#endif

    static bool lookedForFunction = false;
    static ServiceWindowShowFunction dictionaryServiceWindowShow = NULL;

    if (!lookedForFunction) {
        void* langAnalysisFramework = dlopen(frameworkPath, RTLD_LAZY);
        ASSERT(langAnalysisFramework);
        if (langAnalysisFramework)
            dictionaryServiceWindowShow = (ServiceWindowShowFunction)dlsym(langAnalysisFramework, functionName);
        lookedForFunction = true;
    }

    ASSERT(dictionaryServiceWindowShow);
    if (!dictionaryServiceWindowShow) {
        NSLog(@"Couldn't find the %s function in %s", functionName, frameworkPath); 
        return;
    }

    NSAttributedString *attrString = [self selectedAttributedString];

#ifdef BUILDING_ON_TIGER
    // FIXME: must check for right-to-left here
    NSWritingDirection writingDirection = NSWritingDirectionLeftToRight;

    // FIXME: the dictionary API expects the rect for the first line of selection. Passing
    // the rect for the entire selection, as we do here, positions the pop-up window near
    // the bottom of the selection rather than at the selected word.
    NSRect rect = [self convertRect:core([self _frame])->selectionRect() toView:nil];
    rect.origin = [[self window] convertBaseToScreen:rect.origin];
    NSData *data = [attrString RTFFromRange:NSMakeRange(0, [attrString length]) documentAttributes:nil];
    dictionaryServiceWindowShow(data, rect, (writingDirection == NSWritingDirectionRightToLeft) ? 1 : 0);
#else
    // The HIDictionaryWindowShow function requires the origin, in CG screen coordinates, of the first character of text in the selection.
    // FIXME 4945808: We approximate this in a way that works well when a single word is selected, and less well in some other cases
    // (but no worse than we did in Tiger)
    NSRect rect = core([self _frame])->selectionRect();

    NSDictionary *attributes = [attrString fontAttributesInRange:NSMakeRange(0,1)];
    NSFont *font = [attributes objectForKey:NSFontAttributeName];
    if (font)
        rect.origin.y += [font ascender];

    NSPoint windowPoint = [self convertPoint:rect.origin toView:nil];
    NSPoint screenPoint = [[self window] convertBaseToScreen:windowPoint];

    dictionaryServiceWindowShow(nil, attrString, CFRangeMake(0, [attrString length]), nil, 
                                coreGraphicsScreenPointForAppKitScreenPoint(screenPoint), false, nil);
#endif    
}

- (void)_hoverFeedbackSuspendedChanged
{
    [self _updateMouseoverWithFakeEvent];
}

- (BOOL)_interceptEditingKeyEvent:(KeyboardEvent*)event shouldSaveCommand:(BOOL)shouldSave
{
    // Ask AppKit to process the key event -- it will call back with either insertText or doCommandBySelector.
    WebHTMLViewInterpretKeyEventsParameters parameters;
    parameters.eventWasHandled = false;
    parameters.shouldSaveCommand = shouldSave;
    // If we're intercepting the initial IM call we assume that the IM has consumed the event, 
    // and only change this assumption if one of the NSTextInput/Responder callbacks is used.
    // We assume the IM will *not* consume hotkey sequences
    parameters.consumedByIM = !event->metaKey() && shouldSave;
        
    if (const PlatformKeyboardEvent* platformEvent = event->keyEvent()) {
        NSEvent *macEvent = platformEvent->macEvent();
        if ([macEvent type] == NSKeyDown && [_private->compController filterKeyDown:macEvent])
            return true;
        parameters.event = event;
        _private->interpretKeyEventsParameters = &parameters;
        _private->receivedNOOP = NO;
        KeypressCommand command = event->keypressCommand();
        bool hasKeypressCommand = !command.commandNames.isEmpty() || !command.text.isEmpty();

        if (parameters.shouldSaveCommand || !hasKeypressCommand)
            [self interpretKeyEvents:[NSArray arrayWithObject:macEvent]];
        else {
            if (!command.text.isEmpty())
                [self insertText:command.text];
            else {
                size_t size = command.commandNames.size();
                for (size_t i = 0; i < size; ++i)
                    [self doCommandBySelector:NSSelectorFromString(command.commandNames[i])];
            }
        }
        _private->interpretKeyEventsParameters = 0;
    }
    return (!_private->receivedNOOP && parameters.eventWasHandled) || parameters.consumedByIM;
}

- (WebCore::CachedImage*)promisedDragTIFFDataSource 
{
    return _private->promisedDragTIFFDataSource;
}

- (void)setPromisedDragTIFFDataSource:(WebCore::CachedImage*)source
{
    if (source)
        source->ref(promisedDataClient());
    
    if (_private->promisedDragTIFFDataSource)
        _private->promisedDragTIFFDataSource->deref(promisedDataClient());
    _private->promisedDragTIFFDataSource = source;
}

#undef COMMAND_PROLOGUE

@end

@implementation WebHTMLView (WebNSTextInputSupport)

- (NSArray *)validAttributesForMarkedText
{
    static NSArray *validAttributes;
    if (!validAttributes) {
        validAttributes = [[NSArray alloc] initWithObjects:
            NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName,
            NSMarkedClauseSegmentAttributeName, NSTextInputReplacementRangeAttributeName, nil];
        // NSText also supports the following attributes, but it's
        // hard to tell which are really required for text input to
        // work well; I have not seen any input method make use of them yet.
        //     NSFontAttributeName, NSForegroundColorAttributeName,
        //     NSBackgroundColorAttributeName, NSLanguageAttributeName.
        CFRetain(validAttributes);
    }
    LOG(TextInput, "validAttributesForMarkedText -> (...)");
    return validAttributes;
}

// Utility function to make sure we don't return anything through the NSTextInput
// API when an editable region is not currently focused.
static BOOL isTextInput(Frame* coreFrame)
{
    return coreFrame && !coreFrame->selectionController()->isNone() && coreFrame->selectionController()->isContentEditable();
}

- (NSAttributedString *)textStorage
{
    if (!isTextInput(core([self _frame]))) {
        LOG(TextInput, "textStorage -> nil");
        return nil;
    }
    NSAttributedString *result = [self attributedSubstringFromRange:NSMakeRange(0, UINT_MAX)];
    
    LOG(TextInput, "textStorage -> \"%s\"", result ? [[result string] UTF8String] : "");
    
    // We have to return an empty string rather than null to prevent TSM from calling -string
    return result ? result : [[[NSAttributedString alloc] initWithString:@""] autorelease];
}

- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
{
    NSWindow *window = [self window];
    WebFrameBridge *bridge = [self _bridge];

    if (window)
        thePoint = [window convertScreenToBase:thePoint];
    thePoint = [self convertPoint:thePoint fromView:nil];

    DOMRange *range = [bridge characterRangeAtPoint:thePoint];
    if (!range) {
        LOG(TextInput, "characterIndexForPoint:(%f, %f) -> NSNotFound", thePoint.x, thePoint.y);
        return NSNotFound;
    }
    
    unsigned result = [bridge convertDOMRangeToNSRange:range].location;
    LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", thePoint.x, thePoint.y, result);
    return result;
}

- (NSRect)firstRectForCharacterRange:(NSRange)theRange
{    
    WebFrameBridge *bridge = [self _bridge];
    
    // Just to match NSTextView's behavior. Regression tests cannot detect this;
    // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
    // (type something; try ranges (1, -1) and (2, -1).
    if ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0))
        theRange.length = 0;
    
    DOMRange *range = [bridge convertNSRangeToDOMRange:theRange];
    if (!range) {
        LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (0, 0, 0, 0)", theRange.location, theRange.length);
        return NSMakeRect(0, 0, 0, 0);
    }
    
    ASSERT([range startContainer]);
    ASSERT([range endContainer]);
    
    NSRect resultRect = [bridge firstRectForDOMRange:range];
    resultRect = [self convertRect:resultRect toView:nil];

    NSWindow *window = [self window];
    if (window)
        resultRect.origin = [window convertBaseToScreen:resultRect.origin];
    
    LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (%f, %f, %f, %f)", theRange.location, theRange.length, resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height);
    return resultRect;
}

- (NSRange)selectedRange
{
    if (!isTextInput(core([self _frame]))) {
        LOG(TextInput, "selectedRange -> (NSNotFound, 0)");
        return NSMakeRange(NSNotFound, 0);
    }
    NSRange result = [[self _bridge] selectedNSRange];

    LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length);
    return result;
}

- (NSRange)markedRange
{
    if (![self hasMarkedText]) {
        LOG(TextInput, "markedRange -> (NSNotFound, 0)");
        return NSMakeRange(NSNotFound, 0);
    }
    NSRange result = [[self _bridge] markedTextNSRange];

    LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length);
    return result;
}

- (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange
{
    if (!isTextInput(core([self _frame]))) {
        LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length);
        return nil;
    }
    WebFrameBridge *bridge = [self _bridge];
    DOMRange *domRange = [bridge convertNSRangeToDOMRange:nsRange];
    if (!domRange) {
        LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length);
        return nil;
    }

    NSAttributedString *result = [NSAttributedString _web_attributedStringFromRange:core(domRange)];
    
    LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%s\"", nsRange.location, nsRange.length, [[result string] UTF8String]);
    return result;
}

// test for 10.4 because of <rdar://problem/4243463>
#ifdef BUILDING_ON_TIGER
- (long)conversationIdentifier
{
    return (long)self;
}
#else
- (NSInteger)conversationIdentifier
{
    return (NSInteger)self;
}
#endif

- (BOOL)hasMarkedText
{
    BOOL result = [[self _bridge] markedTextDOMRange] != nil;

    LOG(TextInput, "hasMarkedText -> %u", result);
    return result;
}

- (void)unmarkText
{
    LOG(TextInput, "unmarkText");

    // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
    WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
    _private->interpretKeyEventsParameters = 0;

    if (parameters) {
        parameters->eventWasHandled = YES;
        parameters->consumedByIM = NO;
    }
    
    if (Frame* coreFrame = core([self _frame]))
        coreFrame->editor()->unmarkText();
}

- (void)_extractAttributes:(NSArray **)a ranges:(NSArray **)r fromAttributedString:(NSAttributedString *)string
{
    int length = [[string string] length];
    int i = 0;
    NSMutableArray *attributes = [NSMutableArray array];
    NSMutableArray *ranges = [NSMutableArray array];
    while (i < length) {
        NSRange effectiveRange;
        NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&effectiveRange inRange:NSMakeRange(i,length - i)];
        [attributes addObject:attrs];
        [ranges addObject:[NSValue valueWithRange:effectiveRange]];
        i = effectiveRange.location + effectiveRange.length;
    }
    *a = attributes;
    *r = ranges;
}

- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
{
    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString

    LOG(TextInput, "setMarkedText:\"%s\" selectedRange:(%u, %u)", isAttributedString ? [[string string] UTF8String] : [string UTF8String], newSelRange.location, newSelRange.length);

    // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
    WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
    _private->interpretKeyEventsParameters = 0;

    if (parameters) {
        parameters->eventWasHandled = YES;
        parameters->consumedByIM = NO;
    }
    
    Frame* coreFrame = core([self _frame]);
    if (!coreFrame)
        return;

    WebFrameBridge *bridge = [self _bridge];

    if (![self _isEditable])
        return;

    if (isAttributedString) {
        unsigned markedTextLength = [(NSString *)string length];
        NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, markedTextLength)];
        LOG(TextInput, "    ReplacementRange: %s", [rangeString UTF8String]);
        // The AppKit adds a 'secret' property to the string that contains the replacement
        // range.  The replacement range is the range of the the text that should be replaced
        // with the new string.
        if (rangeString)
            [[self _bridge] selectNSRange:NSRangeFromString(rangeString)];
    }

    coreFrame->editor()->setIgnoreMarkedTextSelectionChange(true);

    // if we had marked text already, we need to make sure to replace
    // that, instead of the selection/caret
    coreFrame->editor()->selectMarkedText();

    NSString *text = string;
    NSArray *attributes = nil;
    NSArray *ranges = nil;
    if (isAttributedString) {
        text = [string string];
        [self _extractAttributes:&attributes ranges:&ranges fromAttributedString:string];
    }

    coreFrame->editor()->replaceMarkedText(text);
    [bridge setMarkedTextDOMRange:[self _selectedRange] customAttributes:attributes ranges:ranges];
    if ([self hasMarkedText])
        coreFrame->selectRangeInMarkedText(newSelRange.location, newSelRange.length);

    coreFrame->editor()->setIgnoreMarkedTextSelectionChange(false);
}

- (void)doCommandBySelector:(SEL)selector
{
    LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));

    // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
    // The same call to interpretKeyEvents can do more than one command.
    WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
    if (parameters)
        parameters->consumedByIM = NO;

    if (selector == @selector(noop:)) {
        _private->receivedNOOP = YES;
        return;
    }

    KeyboardEvent* event = parameters ? parameters->event : 0;
    bool shouldSaveCommand = parameters && parameters->shouldSaveCommand;

    if (event && shouldSaveCommand) {
        KeypressCommand command = event->keypressCommand();
        command.commandNames.append(NSStringFromSelector(selector));
        event->setKeypressCommand(command);
    } else {
        // Make sure that only direct calls to doCommandBySelector: see the parameters by setting to 0.
        _private->interpretKeyEventsParameters = 0;

        bool eventWasHandled = true;

        WebView *webView = [self _webView];
        Frame* coreFrame = core([self _frame]);
        if (![[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector] && coreFrame) {
            if (selector == @selector(insertNewline:) || selector == @selector(insertParagraphSeparator:) || selector == @selector(insertNewlineIgnoringFieldEditor:))
                eventWasHandled = coreFrame->editor()->execCommand("InsertNewline", event);
            else if (selector == @selector(insertLineBreak:))
                eventWasHandled = coreFrame->editor()->execCommand("InsertLineBreak", event);
            else if (selector == @selector(insertTab:) || selector == @selector(insertTabIgnoringFieldEditor:))
                eventWasHandled = coreFrame->editor()->execCommand("InsertTab", event);
            else if (selector == @selector(insertBacktab:))
                eventWasHandled = coreFrame->editor()->execCommand("InsertBacktab", event);
            else {
                _private->selectorForDoCommandBySelector = selector;
                [super doCommandBySelector:selector];
                _private->selectorForDoCommandBySelector = 0;
            }
        }

        if (parameters)
            parameters->eventWasHandled = eventWasHandled;

        // Restore the parameters so that other calls to doCommandBySelector: see them,
        // and other commands can participate in setting the "eventWasHandled" flag.
        _private->interpretKeyEventsParameters = parameters;
    }
}

- (void)insertText:(id)string
{
    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString

    LOG(TextInput, "insertText:\"%s\"", isAttributedString ? [[string string] UTF8String] : [string UTF8String]);

    WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
    _private->interpretKeyEventsParameters = 0;
    if (parameters)
        parameters->consumedByIM = NO;

    // We don't support inserting an attributed string but input methods don't appear to require this.
    NSString *text;
    bool isFromInputMethod = [self hasMarkedText];
    if (isAttributedString) {
        text = [string string];
        // We deal with the NSTextInputReplacementRangeAttributeName attribute from NSAttributedString here
        // simply because it is used by at least one Input Method -- it corresonds to the kEventParamTextInputSendReplaceRange
        // event in TSM.  This behaviour matches that of -[WebHTMLView setMarkedText:selectedRange:] when it receives an
        // NSAttributedString
        NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, [text length])];
        LOG(TextInput, "    ReplacementRange: %s", [rangeString UTF8String]);
        if (rangeString) {
            [[self _bridge] selectNSRange:NSRangeFromString(rangeString)];
            isFromInputMethod = YES;
        }
    } else
        text = string;

    bool eventHandled = false;
    if ([text length]) {
        KeyboardEvent* event = parameters ? parameters->event : 0;

        // insertText can be called from an input method or from normal key event processing
        // If its from normal key event processing, we may need to save the action to perform it later.
        // If its from an input method, then we should go ahead and insert the text now.  
        // We assume it's from the input method if we have marked text.
        // FIXME: In theory, this could be wrong for some input methods, so we should try to find
        // another way to determine if the call is from the input method
        bool shouldSaveCommand = parameters && parameters->shouldSaveCommand;
        if (event && shouldSaveCommand && !isFromInputMethod) {
            KeypressCommand command;
            command.text = text;
            event->setKeypressCommand(command);
            return;
        }
        
        Frame* coreFrame = core([self _frame]);
        String eventText = text;
        eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
        eventHandled = coreFrame && coreFrame->editor()->insertText(eventText, event);
    }
    
    if (!parameters)
        return;
    
    if (isFromInputMethod) {
        // Allow doCommandBySelector: to be called after insertText: by resetting interpretKeyEventsParameters
        _private->interpretKeyEventsParameters = parameters;
        parameters->consumedByIM = YES;
        return;
    }
    
    parameters->eventWasHandled = eventHandled;
}

- (BOOL)_selectionIsInsideMarkedText
{
    WebFrameBridge *bridge = [self _bridge];
    DOMRange *selection = [self _selectedRange];
    DOMRange *markedTextRange = [bridge markedTextDOMRange];

    ASSERT([markedTextRange startContainer] == [markedTextRange endContainer]);

    if ([selection startContainer] != [markedTextRange startContainer]) 
        return NO;

    if ([selection endContainer] != [markedTextRange startContainer])
        return NO;

    if ([selection startOffset] < [markedTextRange startOffset])
        return NO;

    if ([selection endOffset] > [markedTextRange endOffset])
        return NO;

    return YES;
}

- (void)_updateSelectionForInputManager
{
    if (![self hasMarkedText])
        return;

    Frame* coreFrame = core([self _frame]);
    if (!coreFrame)
        return;

    if (coreFrame->editor()->ignoreMarkedTextSelectionChange())
        return;

    if ([self _selectionIsInsideMarkedText]) {
        DOMRange *selection = [self _selectedRange];
        DOMRange *markedTextDOMRange = [[self _bridge] markedTextDOMRange];

        unsigned markedSelectionStart = [selection startOffset] - [markedTextDOMRange startOffset];
        unsigned markedSelectionLength = [selection endOffset] - [selection startOffset];
        NSRange newSelectionRange = NSMakeRange(markedSelectionStart, markedSelectionLength);

        [[NSInputManager currentInputManager] markedTextSelectionChanged:newSelectionRange client:self];
    } else {
        [self unmarkText];
        [[NSInputManager currentInputManager] markedTextAbandoned:self];
    }
}

@end

/*
    This class runs the show for handing the complete: NSTextView operation.  It counts on its HTML view
    to call endRevertingChange: whenever the current completion needs to be aborted.
 
    The class is in one of two modes:  PopupWindow showing, or not.  It is shown when a completion yields
    more than one match.  If a completion yields one or zero matches, it is not shown, and **there is no
    state carried across to the next completion**.
 */

@implementation WebTextCompleteController

- (id)initWithHTMLView:(WebHTMLView *)view
{
    self = [super init];
    if (!self)
        return nil;
    _view = view;
    return self;
}

- (void)dealloc
{
    [_popupWindow release];
    [_completions release];
    [_originalString release];
    
    [super dealloc];
}

- (void)_insertMatch:(NSString *)match
{
    // FIXME: 3769654 - We should preserve case of string being inserted, even in prefix (but then also be
    // able to revert that).  Mimic NSText.
    WebFrameBridge *bridge = [_view _bridge];
    NSString *newText = [match substringFromIndex:prefixLength];
    [bridge replaceSelectionWithText:newText selectReplacement:YES smartReplace:NO];
}

// mostly lifted from NSTextView_KeyBinding.m
- (void)_buildUI
{
    NSRect scrollFrame = NSMakeRect(0, 0, 100, 100);
    NSRect tableFrame = NSZeroRect;    
    tableFrame.size = [NSScrollView contentSizeForFrameSize:scrollFrame.size hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder];
    // Added cast to work around problem with multiple Foundation initWithIdentifier: methods with different parameter types.
    NSTableColumn *column = [(NSTableColumn *)[NSTableColumn alloc] initWithIdentifier:[NSNumber numberWithInt:0]];
    [column setWidth:tableFrame.size.width];
    [column setEditable:NO];
    
    _tableView = [[NSTableView alloc] initWithFrame:tableFrame];
    [_tableView setAutoresizingMask:NSViewWidthSizable];
    [_tableView addTableColumn:column];
    [column release];
    [_tableView setDrawsGrid:NO];
    [_tableView setCornerView:nil];
    [_tableView setHeaderView:nil];
    [_tableView setColumnAutoresizingStyle:NSTableViewUniformColumnAutoresizingStyle];
    [_tableView setDelegate:self];
    [_tableView setDataSource:self];
    [_tableView setTarget:self];
    [_tableView setDoubleAction:@selector(tableAction:)];
    
    NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:scrollFrame];
    [scrollView setBorderType:NSNoBorder];
    [scrollView setHasVerticalScroller:YES];
    [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
    [scrollView setDocumentView:_tableView];
    [_tableView release];
    
    _popupWindow = [[NSWindow alloc] initWithContentRect:scrollFrame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
    [_popupWindow setAlphaValue:0.88f];
    [_popupWindow setContentView:scrollView];
    [scrollView release];
    [_popupWindow setHasShadow:YES];
    [_popupWindow setOneShot:YES];
    [_popupWindow _setForceActiveControls:YES];
    [_popupWindow setReleasedWhenClosed:NO];
}

// mostly lifted from NSTextView_KeyBinding.m
- (void)_placePopupWindow:(NSPoint)topLeft
{
    int numberToShow = [_completions count];
    if (numberToShow > 20) {
        numberToShow = 20;
    }

    NSRect windowFrame;
    NSPoint wordStart = topLeft;
    windowFrame.origin = [[_view window] convertBaseToScreen:[_view convertPoint:wordStart toView:nil]];
    windowFrame.size.height = numberToShow * [_tableView rowHeight] + (numberToShow + 1) * [_tableView intercellSpacing].height;
    windowFrame.origin.y -= windowFrame.size.height;
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:12.0f], NSFontAttributeName, nil];
    float maxWidth = 0.0f;
    int maxIndex = -1;
    int i;
    for (i = 0; i < numberToShow; i++) {
        float width = ceilf([[_completions objectAtIndex:i] sizeWithAttributes:attributes].width);
        if (width > maxWidth) {
            maxWidth = width;
            maxIndex = i;
        }
    }
    windowFrame.size.width = 100;
    if (maxIndex >= 0) {
        maxWidth = ceilf([NSScrollView frameSizeForContentSize:NSMakeSize(maxWidth, 100.0f) hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder].width);
        maxWidth = ceilf([NSWindow frameRectForContentRect:NSMakeRect(0.0f, 0.0f, maxWidth, 100.0f) styleMask:NSBorderlessWindowMask].size.width);
        maxWidth += 5.0f;
        windowFrame.size.width = MAX(maxWidth, windowFrame.size.width);
        maxWidth = MIN(400.0f, windowFrame.size.width);
    }
    [_popupWindow setFrame:windowFrame display:NO];
    
    [_tableView reloadData];
    [_tableView selectRow:0 byExtendingSelection:NO];
    [_tableView scrollRowToVisible:0];
    [self _reflectSelection];
    [_popupWindow setLevel:NSPopUpMenuWindowLevel];
    [_popupWindow orderFront:nil];    
    [[_view window] addChildWindow:_popupWindow ordered:NSWindowAbove];
}

- (void)doCompletion
{
    if (!_popupWindow) {
        NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
        if (!checker) {
            LOG_ERROR("No NSSpellChecker");
            return;
        }

        // Get preceeding word stem
        WebFrameBridge *bridge = [_view _bridge];
        DOMRange *selection = kit(core([_view _frame])->selectionController()->toRange().get());
        DOMRange *wholeWord = [bridge rangeByAlteringCurrentSelection:SelectionController::EXTEND
            direction:SelectionController::BACKWARD granularity:WordGranularity];
        DOMRange *prefix = [wholeWord cloneRange];
        [prefix setEnd:[selection startContainer] offset:[selection startOffset]];

        // Reject some NOP cases
        if ([prefix collapsed]) {
            NSBeep();
            return;
        }
        NSString *prefixStr = [bridge stringForRange:prefix];
        NSString *trimmedPrefix = [prefixStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        if ([trimmedPrefix length] == 0) {
            NSBeep();
            return;
        }
        prefixLength = [prefixStr length];

        // Lookup matches
        [_completions release];
        _completions = [checker completionsForPartialWordRange:NSMakeRange(0, [prefixStr length]) inString:prefixStr language:nil inSpellDocumentWithTag:[[_view _webView] spellCheckerDocumentTag]];
        [_completions retain];
    
        if (!_completions || [_completions count] == 0) {
            NSBeep();
        } else if ([_completions count] == 1) {
            [self _insertMatch:[_completions objectAtIndex:0]];
        } else {
            ASSERT(!_originalString);       // this should only be set IFF we have a popup window
            _originalString = [[bridge stringForRange:selection] retain];
            [self _buildUI];
            NSRect wordRect = [bridge caretRectAtNode:[wholeWord startContainer] offset:[wholeWord startOffset] affinity:NSSelectionAffinityDownstream];
            // +1 to be under the word, not the caret
            // FIXME - 3769652 - Wrong positioning for right to left languages.  We should line up the upper
            // right corner with the caret instead of upper left, and the +1 would be a -1.
            NSPoint wordLowerLeft = { NSMinX(wordRect)+1, NSMaxY(wordRect) };
            [self _placePopupWindow:wordLowerLeft];
        }
    } else {
        [self endRevertingChange:YES moveLeft:NO];
    }
}

- (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft
{
    if (_popupWindow) {
        // tear down UI
        [[_view window] removeChildWindow:_popupWindow];
        [_popupWindow orderOut:self];
        // Must autorelease because event tracking code may be on the stack touching UI
        [_popupWindow autorelease];
        _popupWindow = nil;

        if (revertChange) {
            WebFrameBridge *bridge = [_view _bridge];
            [bridge replaceSelectionWithText:_originalString selectReplacement:YES smartReplace:NO];
        } else if ([_view _hasSelection]) {
            if (goLeft)
                [_view moveBackward:nil];
            else
                [_view moveForward:nil];
        }
        [_originalString release];
        _originalString = nil;
    }
    // else there is no state to abort if the window was not up
}

- (BOOL)popupWindowIsOpen
{
    return _popupWindow != nil;
}

// WebHTMLView gives us a crack at key events it sees.  Return whether we consumed the event.
// The features for the various keys mimic NSTextView.
- (BOOL)filterKeyDown:(NSEvent *)event
{
    if (_popupWindow) {
        NSString *string = [event charactersIgnoringModifiers];
        unichar c = [string characterAtIndex:0];
        if (c == NSUpArrowFunctionKey) {
            int selectedRow = [_tableView selectedRow];
            if (0 < selectedRow) {
                [_tableView selectRow:selectedRow-1 byExtendingSelection:NO];
                [_tableView scrollRowToVisible:selectedRow-1];
            }
            return YES;
        } else if (c == NSDownArrowFunctionKey) {
            int selectedRow = [_tableView selectedRow];
            if (selectedRow < (int)[_completions count]-1) {
                [_tableView selectRow:selectedRow+1 byExtendingSelection:NO];
                [_tableView scrollRowToVisible:selectedRow+1];
            }
            return YES;
        } else if (c == NSRightArrowFunctionKey || c == '\n' || c == '\r' || c == '\t') {
            [self endRevertingChange:NO moveLeft:NO];
            return YES;
        } else if (c == NSLeftArrowFunctionKey) {
            [self endRevertingChange:NO moveLeft:YES];
            return YES;
        } else if (c == 0x1b || c == NSF5FunctionKey) {
            [self endRevertingChange:YES moveLeft:NO];
            return YES;
        } else if (c == ' ' || ispunct(c)) {
            [self endRevertingChange:NO moveLeft:NO];
            return NO;  // let the char get inserted
        }
    }
    return NO;
}

- (void)_reflectSelection
{
    int selectedRow = [_tableView selectedRow];
    ASSERT(selectedRow >= 0 && selectedRow < (int)[_completions count]);
    [self _insertMatch:[_completions objectAtIndex:selectedRow]];
}

- (void)tableAction:(id)sender
{
    [self _reflectSelection];
    [self endRevertingChange:NO moveLeft:NO];
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
    return [_completions count];
}

- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
    return [_completions objectAtIndex:row];
}

- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
    [self _reflectSelection];
}

@end

@implementation WebHTMLView (WebDocumentPrivateProtocols)

- (NSRect)selectionRect
{
    if ([self _hasSelection])
        return core([self _frame])->selectionRect();
    return NSZeroRect;
}

- (NSArray *)selectionTextRects
{
    if (![self _hasSelection])
        return nil;
    
    Vector<FloatRect> list;
    core([self _frame])->selectionTextRects(list);

    unsigned size = list.size();
    NSMutableArray *result = [[[NSMutableArray alloc] initWithCapacity:size] autorelease];
    for (unsigned i = 0; i < size; ++i)
        [result addObject:[NSValue valueWithRect:list[i]]];
    
    return result;
}

- (NSView *)selectionView
{
    return self;
}

- (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText
{
    if ([self _hasSelection])
        return core([self _frame])->selectionImage(forceBlackText);
    return nil;
}

- (NSImage *)selectionImageForcingWhiteText:(BOOL)forceWhiteText
{
    // NOTE: this method is obsolete and doesn't behave as its name suggests.
    // See comment in WebDocumentPrivate.h.
    return [self selectionImageForcingBlackText:forceWhiteText];
}

- (NSRect)selectionImageRect
{
    if ([self _hasSelection])
        return core([self _frame])->selectionRect();
    return NSZeroRect;
}

- (NSArray *)pasteboardTypesForSelection
{
    if ([self _canSmartCopyOrDelete]) {
        NSMutableArray *types = [[[[self class] _selectionPasteboardTypes] mutableCopy] autorelease];
        [types addObject:WebSmartPastePboardType];
        return types;
    } else {
        return [[self class] _selectionPasteboardTypes];
    }
}

- (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard
{
    [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:nil];
}

- (void)selectAll
{
    Frame* coreFrame = core([self _frame]);
    if (coreFrame)
        coreFrame->selectionController()->selectAll();
}

- (void)deselectAll
{
    Frame* coreFrame = core([self _frame]);
    if (!coreFrame)
        return;
    coreFrame->selectionController()->clear();
}

- (NSString *)string
{
    return [[self _bridge] stringForRange:[self _documentRange]];
}

- (NSAttributedString *)_attributeStringFromDOMRange:(DOMRange *)range
{
    NSAttributedString *attributedString;
#if !LOG_DISABLED        
    double start = CFAbsoluteTimeGetCurrent();
#endif    
    attributedString = [[[NSAttributedString alloc] _initWithDOMRange:range] autorelease];
#if !LOG_DISABLED
    double duration = CFAbsoluteTimeGetCurrent() - start;
    LOG(Timing, "creating attributed string from selection took %f seconds.", duration);
#endif
    return attributedString;
}

- (NSAttributedString *)attributedString
{
    DOMDocument *document = [[self _frame] DOMDocument];
    NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[document _documentRange]];
    if (!attributedString) {
        Document* coreDocument = core(document);
        Range range(coreDocument, coreDocument, 0, 0, 0);
        attributedString = [NSAttributedString _web_attributedStringFromRange:&range];
    }
    return attributedString;
}

- (NSString *)selectedString
{
    return [[self _bridge] selectedString];
}

- (NSAttributedString *)selectedAttributedString
{
    NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[self _selectedRange]];
    if (!attributedString) {
        Frame* coreFrame = core([self _frame]);
        if (coreFrame) {
            RefPtr<Range> range = coreFrame->selectionController()->selection().toRange();
            attributedString = [NSAttributedString _web_attributedStringFromRange:range.get()];
        }
    }
    return attributedString;
}

- (BOOL)supportsTextEncoding
{
    return YES;
}

- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection
{
    if (![string length])
        return NO;
    
    return [[self _bridge] searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:startInSelection];
}

@end

@implementation WebHTMLView (WebDocumentInternalProtocols)

- (NSDictionary *)elementAtPoint:(NSPoint)point
{
    return [self elementAtPoint:point allowShadowContent:NO];
}

- (NSDictionary *)elementAtPoint:(NSPoint)point allowShadowContent:(BOOL)allow;
{
    Frame* coreframe = core([self _frame]);
    if (coreframe) 
        return [[[WebElementDictionary alloc] initWithHitTestResult:coreframe->eventHandler()->hitTestResultAtPoint(IntPoint(point), allow)] autorelease];
    return nil;
}

- (NSUInteger)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag limit:(NSUInteger)limit
{
    return [[self _bridge] markAllMatchesForText:string caseSensitive:caseFlag limit:limit];
}

- (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue
{
    [[self _bridge] setMarkedTextMatchesAreHighlighted:newValue];
}

- (BOOL)markedTextMatchesAreHighlighted
{
    return [[self _bridge] markedTextMatchesAreHighlighted];
}

- (void)unmarkAllTextMatches
{
    return [[self _bridge] unmarkAllTextMatches];
}

- (NSArray *)rectsForTextMatches
{
    return [[self _bridge] rectsForTextMatches];
}

@end

// This is used by AppKit and is included here so that WebDataProtocolScheme is only defined once.
@implementation NSURL (WebDataURL)

+ (NSURL *)_web_uniqueWebDataURL
{
    CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault);
    NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef);
    CFRelease(UUIDRef);
    NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", WebDataProtocolScheme, UUIDString]];
    CFRelease(UUIDString);
    return URL;
}

@end
