1/*
2 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
3 *           (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#import "WebHTMLView.h"
31
32#import "DOMCSSStyleDeclarationInternal.h"
33#import "DOMDocumentFragmentInternal.h"
34#import "DOMDocumentInternal.h"
35#import "DOMNodeInternal.h"
36#import "DOMRangeInternal.h"
37#import "WebArchive.h"
38#import "WebClipView.h"
39#import "WebDOMOperationsInternal.h"
40#import "WebDataSourceInternal.h"
41#import "WebDefaultUIDelegate.h"
42#import "WebDelegateImplementationCaching.h"
43#import "WebDocumentInternal.h"
44#import "WebDynamicScrollBarsViewInternal.h"
45#import "WebEditingDelegate.h"
46#import "WebElementDictionary.h"
47#import "WebFrameInternal.h"
48#import "WebFramePrivate.h"
49#import "WebFrameViewInternal.h"
50#import "WebHTMLRepresentationPrivate.h"
51#import "WebHTMLViewInternal.h"
52#import "WebKitLogging.h"
53#import "WebKitNSStringExtras.h"
54#import "WebKitVersionChecks.h"
55#import "WebLocalizableStringsInternal.h"
56#import "WebNSEventExtras.h"
57#import "WebNSFileManagerExtras.h"
58#import "WebNSImageExtras.h"
59#import "WebNSObjectExtras.h"
60#import "WebNSPasteboardExtras.h"
61#import "WebNSPrintOperationExtras.h"
62#import "WebNSURLExtras.h"
63#import "WebNSViewExtras.h"
64#import "WebNetscapePluginView.h"
65#import "WebNodeHighlight.h"
66#import "WebPluginController.h"
67#import "WebPreferences.h"
68#import "WebPreferencesPrivate.h"
69#import "WebResourcePrivate.h"
70#import "WebTextCompletionController.h"
71#import "WebTypesInternal.h"
72#import "WebUIDelegatePrivate.h"
73#import "WebViewInternal.h"
74#import <AppKit/NSAccessibility.h>
75#import <ApplicationServices/ApplicationServices.h>
76#import <WebCore/CSSStyleDeclaration.h>
77#import <WebCore/CachedImage.h>
78#import <WebCore/CachedResourceClient.h>
79#import <WebCore/CachedResourceLoader.h>
80#import <WebCore/Chrome.h>
81#import <WebCore/ColorMac.h>
82#import <WebCore/ContextMenu.h>
83#import <WebCore/ContextMenuController.h>
84#import <WebCore/Document.h>
85#import <WebCore/DocumentFragment.h>
86#import <WebCore/DocumentMarkerController.h>
87#import <WebCore/DragController.h>
88#import <WebCore/Editor.h>
89#import <WebCore/EditorDeleteAction.h>
90#import <WebCore/Element.h>
91#import <WebCore/EventHandler.h>
92#import <WebCore/ExceptionHandlers.h>
93#import <WebCore/FloatRect.h>
94#import <WebCore/FocusController.h>
95#import <WebCore/Frame.h>
96#import <WebCore/FrameLoader.h>
97#import <WebCore/FrameSelection.h>
98#import <WebCore/FrameSnapshottingMac.h>
99#import <WebCore/FrameView.h>
100#import <WebCore/HTMLConverter.h>
101#import <WebCore/HTMLNames.h>
102#import <WebCore/HitTestResult.h>
103#import <WebCore/Image.h>
104#import <WebCore/KeyboardEvent.h>
105#import <WebCore/LegacyWebArchive.h>
106#import <WebCore/MIMETypeRegistry.h>
107#import <WebCore/Page.h>
108#import <WebCore/PlatformEventFactoryMac.h>
109#import <WebCore/Range.h>
110#import <WebCore/RenderView.h>
111#import <WebCore/RenderWidget.h>
112#import <WebCore/ResourceBuffer.h>
113#import <WebCore/RunLoop.h>
114#import <WebCore/RuntimeApplicationChecks.h>
115#import <WebCore/SharedBuffer.h>
116#import <WebCore/SimpleFontData.h>
117#import <WebCore/StylePropertySet.h>
118#import <WebCore/Text.h>
119#import <WebCore/TextAlternativeWithRange.h>
120#import <WebCore/WebCoreObjCExtras.h>
121#import <WebCore/WebFontCache.h>
122#import <WebCore/WebNSAttributedStringExtras.h>
123#import <WebCore/markup.h>
124#import <WebKit/DOM.h>
125#import <WebKit/DOMExtensions.h>
126#import <WebKit/DOMPrivate.h>
127#import <WebKitSystemInterface.h>
128#import <dlfcn.h>
129#import <limits>
130#import <runtime/InitializeThreading.h>
131#import <wtf/MainThread.h>
132#import <wtf/ObjcRuntimeExtras.h>
133
134#if USE(ACCELERATED_COMPOSITING)
135#import <QuartzCore/QuartzCore.h>
136#endif
137
138using namespace WebCore;
139using namespace HTMLNames;
140using namespace WTF;
141
142@interface WebMenuTarget : NSObject {
143    WebCore::ContextMenuController* _menuController;
144}
145+ (WebMenuTarget*)sharedMenuTarget;
146- (WebCore::ContextMenuController*)menuController;
147- (void)setMenuController:(WebCore::ContextMenuController*)menuController;
148- (void)forwardContextMenuAction:(id)sender;
149- (BOOL)validateMenuItem:(NSMenuItem *)item;
150@end
151
152static WebMenuTarget* target;
153
154@implementation WebMenuTarget
155
156+ (WebMenuTarget*)sharedMenuTarget
157{
158    if (!target)
159        target = [[WebMenuTarget alloc] init];
160    return target;
161}
162
163- (WebCore::ContextMenuController*)menuController
164{
165    return _menuController;
166}
167
168- (void)setMenuController:(WebCore::ContextMenuController*)menuController
169{
170    _menuController = menuController;
171}
172
173- (void)forwardContextMenuAction:(id)sender
174{
175    WebCore::ContextMenuItem item(WebCore::ActionType, static_cast<WebCore::ContextMenuAction>([sender tag]), [sender title]);
176    _menuController->contextMenuItemSelected(&item);
177}
178
179- (BOOL)validateMenuItem:(NSMenuItem *)item
180{
181    WebCore::ContextMenuItem coreItem(item);
182    ASSERT(_menuController->contextMenu());
183    _menuController->checkOrEnableIfNeeded(coreItem);
184    return coreItem.enabled();
185}
186
187@end
188
189@interface NSWindow (BorderViewAccess)
190- (NSView*)_web_borderView;
191@end
192
193@implementation NSWindow (BorderViewAccess)
194- (NSView*)_web_borderView
195{
196    return _borderView;
197}
198@end
199
200@interface WebResponderChainSink : NSResponder {
201    NSResponder* _lastResponderInChain;
202    BOOL _receivedUnhandledCommand;
203}
204- (id)initWithResponderChain:(NSResponder *)chain;
205- (void)detach;
206- (BOOL)receivedUnhandledCommand;
207@end
208
209@interface WebLayerHostingFlippedView : NSView
210@end
211
212@implementation WebLayerHostingFlippedView
213- (BOOL)isFlipped
214{
215    return YES;
216}
217@end
218
219@interface WebRootLayer : CALayer
220@end
221
222@implementation WebRootLayer
223- (void)renderInContext:(CGContextRef)ctx
224{
225    // AppKit calls -[CALayer renderInContext:] to render layer-backed views
226    // into bitmap contexts, but renderInContext: doesn't capture mask layers
227    // (<rdar://problem/9539526>), so we can't rely on it. Since our layer
228    // contents will have already been rendered by drawRect:, we can safely make
229    // this a NOOP.
230}
231@end
232
233// if YES, do the standard NSView hit test (which can't give the right result when HTML overlaps a view)
234static BOOL forceNSViewHitTest;
235
236// 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])
237static BOOL forceWebHTMLViewHitTest;
238
239static WebHTMLView *lastHitView;
240
241static bool needsCursorRectsSupportAtPoint(NSWindow* window, NSPoint point)
242{
243    forceNSViewHitTest = YES;
244    NSView* view = [[window _web_borderView] hitTest:point];
245    forceNSViewHitTest = NO;
246
247    // WebHTMLView doesn't use cursor rects.
248    if ([view isKindOfClass:[WebHTMLView class]])
249        return false;
250
251#if ENABLE(NETSCAPE_PLUGIN_API)
252    // Neither do NPAPI plug-ins.
253    if ([view isKindOfClass:[WebBaseNetscapePluginView class]])
254        return false;
255#endif
256
257    // Non-Web content, WebPDFView, and WebKit plug-ins use normal cursor handling.
258    return true;
259}
260
261
262static IMP oldSetCursorForMouseLocationIMP;
263
264// Overriding an internal method is a hack; <rdar://problem/7662987> tracks finding a better solution.
265static void setCursor(NSWindow *self, SEL cmd, NSPoint point)
266{
267    if (needsCursorRectsSupportAtPoint(self, point))
268        wtfCallIMP<id>(oldSetCursorForMouseLocationIMP, self, cmd, point);
269}
270
271
272extern "C" {
273
274// Need to declare these attribute names because AppKit exports them but does not make them available in API or SPI headers.
275
276extern NSString *NSMarkedClauseSegmentAttributeName;
277extern NSString *NSTextInputReplacementRangeAttributeName;
278
279}
280
281@interface NSView (WebNSViewDetails)
282- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
283- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
284- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView;
285- (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext CGContext:(CGContextRef)ctx topView:(BOOL)isTopView shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor;
286- (NSRect)_dirtyRect;
287- (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants;
288- (BOOL)_drawnByAncestor;
289- (void)_invalidateGStatesForTree;
290- (void)_propagateDirtyRectsToOpaqueAncestors;
291- (void)_windowChangedKeyState;
292@end
293
294#if USE(ACCELERATED_COMPOSITING)
295static IMP oldSetNeedsDisplayInRectIMP;
296
297static void setNeedsDisplayInRect(NSView *self, SEL cmd, NSRect invalidRect)
298{
299    if (![self _drawnByAncestor]) {
300        wtfCallIMP<id>(oldSetNeedsDisplayInRectIMP, self, cmd, invalidRect);
301        return;
302    }
303
304    static Class webFrameViewClass = [WebFrameView class];
305    WebFrameView *enclosingWebFrameView = (WebFrameView *)self;
306    while (enclosingWebFrameView && ![enclosingWebFrameView isKindOfClass:webFrameViewClass])
307        enclosingWebFrameView = (WebFrameView *)[enclosingWebFrameView superview];
308
309    if (!enclosingWebFrameView) {
310        wtfCallIMP<id>(oldSetNeedsDisplayInRectIMP, self, cmd, invalidRect);
311        return;
312    }
313
314    Frame* coreFrame = core([enclosingWebFrameView webFrame]);
315    FrameView* frameView = coreFrame ? coreFrame->view() : 0;
316    if (!frameView || !frameView->isEnclosedInCompositingLayer()) {
317        wtfCallIMP<id>(oldSetNeedsDisplayInRectIMP, self, cmd, invalidRect);
318        return;
319    }
320
321    NSRect invalidRectInWebFrameViewCoordinates = [enclosingWebFrameView convertRect:invalidRect fromView:self];
322    IntRect invalidRectInFrameViewCoordinates(invalidRectInWebFrameViewCoordinates);
323    if (![enclosingWebFrameView isFlipped])
324        invalidRectInFrameViewCoordinates.setY(frameView->frameRect().size().height() - invalidRectInFrameViewCoordinates.maxY());
325
326    frameView->invalidateRect(invalidRectInFrameViewCoordinates);
327}
328#endif // USE(ACCELERATED_COMPOSITING)
329
330@interface NSApplication (WebNSApplicationDetails)
331- (void)speakString:(NSString *)string;
332@end
333
334@interface NSWindow (WebNSWindowDetails)
335- (id)_newFirstResponderAfterResigning;
336@end
337
338@interface NSAttributedString (WebNSAttributedStringDetails)
339- (id)_initWithDOMRange:(DOMRange *)range;
340- (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
341@end
342
343@interface NSSpellChecker (WebNSSpellCheckerDetails)
344- (void)learnWord:(NSString *)word;
345@end
346
347// By imaging to a width a little wider than the available pixels,
348// thin pages will be scaled down a little, matching the way they
349// print in IE and Camino. This lets them use fewer sheets than they
350// would otherwise, which is presumably why other browsers do this.
351// Wide pages will be scaled down more than this.
352const float _WebHTMLViewPrintingMinimumShrinkFactor = 1.25;
353
354// This number determines how small we are willing to reduce the page content
355// in order to accommodate the widest line. If the page would have to be
356// reduced smaller to make the widest line fit, we just clip instead (this
357// behavior matches MacIE and Mozilla, at least)
358const float _WebHTMLViewPrintingMaximumShrinkFactor = 2;
359
360#define AUTOSCROLL_INTERVAL             0.1f
361
362// Any non-zero value will do, but using something recognizable might help us debug some day.
363#define TRACKING_RECT_TAG 0xBADFACE
364
365// FIXME: This constant is copied from AppKit's _NXSmartPaste constant.
366#define WebSmartPastePboardType @"NeXT smart paste pasteboard type"
367
368#define STANDARD_WEIGHT 5
369#define MIN_BOLD_WEIGHT 7
370#define STANDARD_BOLD_WEIGHT 9
371
372// Fake URL scheme.
373#define WebDataProtocolScheme @"webkit-fake-url"
374
375// <rdar://problem/4985524> References to WebCoreScrollView as a subview of a WebHTMLView may be present
376// in some NIB files, so NSUnarchiver must be still able to look up this now-unused class.
377@interface WebCoreScrollView : NSScrollView
378@end
379
380@implementation WebCoreScrollView
381@end
382
383// We need this to be able to safely reference the CachedImage for the promised drag data
384static CachedImageClient* promisedDataClient()
385{
386    static CachedImageClient* staticCachedResourceClient = new CachedImageClient;
387    return staticCachedResourceClient;
388}
389
390@interface WebHTMLView (WebHTMLViewFileInternal)
391- (BOOL)_imageExistsAtPaths:(NSArray *)paths;
392- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText;
393- (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard;
394- (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
395- (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard;
396- (void)_removeMouseMovedObserverUnconditionally;
397- (void)_removeSuperviewObservers;
398- (void)_removeWindowObservers;
399- (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
400- (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
401- (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action;
402- (DOMRange *)_selectedRange;
403- (BOOL)_shouldDeleteRange:(DOMRange *)range;
404- (NSView *)_hitViewForEvent:(NSEvent *)event;
405- (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString;
406- (DOMRange *)_documentRange;
407- (void)_setMouseDownEvent:(NSEvent *)event;
408- (WebHTMLView *)_topHTMLView;
409- (BOOL)_isTopHTMLView;
410- (void)_web_setPrintingModeRecursive;
411- (void)_web_setPrintingModeRecursiveAndAdjustViewSize;
412- (void)_web_clearPrintingModeRecursive;
413@end
414
415@interface WebHTMLView (WebHTMLViewTextCheckingInternal)
416- (void)orderFrontSubstitutionsPanel:(id)sender;
417- (BOOL)smartInsertDeleteEnabled;
418- (void)setSmartInsertDeleteEnabled:(BOOL)flag;
419- (void)toggleSmartInsertDelete:(id)sender;
420- (BOOL)isAutomaticQuoteSubstitutionEnabled;
421- (void)setAutomaticQuoteSubstitutionEnabled:(BOOL)flag;
422- (void)toggleAutomaticQuoteSubstitution:(id)sender;
423- (BOOL)isAutomaticLinkDetectionEnabled;
424- (void)setAutomaticLinkDetectionEnabled:(BOOL)flag;
425- (void)toggleAutomaticLinkDetection:(id)sender;
426- (BOOL)isAutomaticDashSubstitutionEnabled;
427- (void)setAutomaticDashSubstitutionEnabled:(BOOL)flag;
428- (void)toggleAutomaticDashSubstitution:(id)sender;
429- (BOOL)isAutomaticTextReplacementEnabled;
430- (void)setAutomaticTextReplacementEnabled:(BOOL)flag;
431- (void)toggleAutomaticTextReplacement:(id)sender;
432- (BOOL)isAutomaticSpellingCorrectionEnabled;
433- (void)setAutomaticSpellingCorrectionEnabled:(BOOL)flag;
434- (void)toggleAutomaticSpellingCorrection:(id)sender;
435@end
436
437@interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in a normal category and stop doing the forward declaration trick.
438- (void)_setPrinting:(BOOL)printing minimumPageLogicalWidth:(float)minPageWidth logicalHeight:(float)minPageHeight originalPageWidth:(float)pageLogicalWidth originalPageHeight:(float)pageLogicalHeight maximumShrinkRatio:(float)maximumShrinkRatio adjustViewSize:(BOOL)adjustViewSize paginateScreenContent:(BOOL)paginateScreenContent;
439- (void)_updateSecureInputState;
440@end
441
442@class NSTextInputContext;
443@interface NSResponder (AppKitDetails)
444- (NSTextInputContext *)inputContext;
445@end
446
447@interface NSObject (NSTextInputContextDetails)
448- (BOOL)wantsToHandleMouseEvents;
449- (BOOL)handleMouseEvent:(NSEvent *)event;
450@end
451
452@interface WebHTMLView (WebNSTextInputSupport) <NSTextInput>
453- (void)_updateSelectionForInputManager;
454@end
455
456@interface WebHTMLView (WebEditingStyleSupport)
457- (DOMCSSStyleDeclaration *)_emptyStyle;
458- (NSString *)_colorAsString:(NSColor *)color;
459@end
460
461@interface NSView (WebHTMLViewFileInternal)
462- (void)_web_addDescendantWebHTMLViewsToArray:(NSMutableArray *) array;
463@end
464
465@interface NSMutableDictionary (WebHTMLViewFileInternal)
466- (void)_web_setObjectIfNotNil:(id)object forKey:(id)key;
467@end
468
469struct WebHTMLViewInterpretKeyEventsParameters {
470    KeyboardEvent* event;
471    bool eventInterpretationHadSideEffects;
472    bool shouldSaveCommands;
473    bool consumedByIM;
474    bool executingSavedKeypressCommands;
475};
476
477@interface WebHTMLViewPrivate : NSObject {
478@public
479    BOOL closed;
480    BOOL ignoringMouseDraggedEvents;
481    BOOL printing;
482    BOOL paginateScreenContent;
483    BOOL observingMouseMovedNotifications;
484    BOOL observingSuperviewNotifications;
485    BOOL observingWindowNotifications;
486
487    id savedSubviews;
488    BOOL subviewsSetAside;
489
490#if USE(ACCELERATED_COMPOSITING)
491    NSView *layerHostingView;
492    BOOL drawingIntoLayer;
493#endif
494
495    NSEvent *mouseDownEvent; // Kept after handling the event.
496    BOOL handlingMouseDownEvent;
497    NSEvent *keyDownEvent; // Kept after handling the event.
498
499    // A WebHTMLView has a single input context, but we return nil when in non-editable content to avoid making input methods do their work.
500    // This state is saved each time selection changes, because computing it causes style recalc, which is not always safe to do.
501    BOOL exposeInputContext;
502
503    // Track whether the view has set a secure input state.
504    BOOL isInSecureInputState;
505
506    BOOL _forceUpdateSecureInputState;
507
508    NSPoint lastScrollPosition;
509    BOOL inScrollPositionChanged;
510
511    WebPluginController *pluginController;
512
513    NSString *toolTip;
514    NSToolTipTag lastToolTipTag;
515    id trackingRectOwner;
516    void *trackingRectUserData;
517
518    NSTimer *autoscrollTimer;
519    NSEvent *autoscrollTriggerEvent;
520
521    NSArray *pageRects;
522
523    NSMutableDictionary *highlighters;
524
525
526    WebTextCompletionController *completionController;
527
528    BOOL transparentBackground;
529
530    WebHTMLViewInterpretKeyEventsParameters* interpretKeyEventsParameters;
531
532    WebDataSource *dataSource;
533    WebCore::CachedImage* promisedDragTIFFDataSource;
534
535    SEL selectorForDoCommandBySelector;
536
537    NSTrackingArea *trackingAreaForNonKeyWindow;
538
539#ifndef NDEBUG
540    BOOL enumeratingSubviews;
541#endif
542}
543- (void)clear;
544@end
545
546static NSCellStateValue kit(TriState state)
547{
548    switch (state) {
549        case FalseTriState:
550            return NSOffState;
551        case TrueTriState:
552            return NSOnState;
553        case MixedTriState:
554            return NSMixedState;
555    }
556    ASSERT_NOT_REACHED();
557    return NSOffState;
558}
559
560@implementation WebHTMLViewPrivate
561
562+ (void)initialize
563{
564    JSC::initializeThreading();
565    WTF::initializeMainThreadToProcessMainThread();
566    WebCore::RunLoop::initializeMainRunLoop();
567    WebCoreObjCFinalizeOnMainThread(self);
568
569    if (!oldSetCursorForMouseLocationIMP) {
570        Method setCursorMethod = class_getInstanceMethod([NSWindow class], @selector(_setCursorForMouseLocation:));
571        ASSERT(setCursorMethod);
572        oldSetCursorForMouseLocationIMP = method_setImplementation(setCursorMethod, (IMP)setCursor);
573        ASSERT(oldSetCursorForMouseLocationIMP);
574    }
575
576#if USE(ACCELERATED_COMPOSITING)
577    if (!oldSetNeedsDisplayInRectIMP) {
578        Method setNeedsDisplayInRectMethod = class_getInstanceMethod([NSView class], @selector(setNeedsDisplayInRect:));
579        ASSERT(setNeedsDisplayInRectMethod);
580        oldSetNeedsDisplayInRectIMP = method_setImplementation(setNeedsDisplayInRectMethod, (IMP)setNeedsDisplayInRect);
581        ASSERT(oldSetNeedsDisplayInRectIMP);
582    }
583#endif // USE(ACCELERATED_COMPOSITING)
584
585
586}
587
588- (void)dealloc
589{
590    if (WebCoreObjCScheduleDeallocateOnMainThread([WebHTMLViewPrivate class], self))
591        return;
592
593    ASSERT(!autoscrollTimer);
594    ASSERT(!autoscrollTriggerEvent);
595
596    [mouseDownEvent release];
597    [keyDownEvent release];
598    [pluginController release];
599    [toolTip release];
600    [completionController release];
601    [dataSource release];
602    [highlighters release];
603    [trackingAreaForNonKeyWindow release];
604    if (promisedDragTIFFDataSource)
605        promisedDragTIFFDataSource->removeClient(promisedDataClient());
606
607    [super dealloc];
608}
609
610- (void)finalize
611{
612    ASSERT_MAIN_THREAD();
613
614    if (promisedDragTIFFDataSource)
615        promisedDragTIFFDataSource->removeClient(promisedDataClient());
616
617    [super finalize];
618}
619
620- (void)clear
621{
622    [mouseDownEvent release];
623    [keyDownEvent release];
624    [pluginController release];
625    [toolTip release];
626    [completionController release];
627    [dataSource release];
628    [highlighters release];
629    [trackingAreaForNonKeyWindow release];
630    if (promisedDragTIFFDataSource)
631        promisedDragTIFFDataSource->removeClient(promisedDataClient());
632
633    mouseDownEvent = nil;
634    keyDownEvent = nil;
635    pluginController = nil;
636    toolTip = nil;
637    completionController = nil;
638    dataSource = nil;
639    highlighters = nil;
640    trackingAreaForNonKeyWindow = nil;
641    promisedDragTIFFDataSource = 0;
642
643#if USE(ACCELERATED_COMPOSITING)
644    layerHostingView = nil;
645#endif
646}
647
648@end
649
650@implementation WebHTMLView (WebHTMLViewFileInternal)
651
652- (DOMRange *)_documentRange
653{
654    return [[[self _frame] DOMDocument] _documentRange];
655}
656
657- (BOOL)_imageExistsAtPaths:(NSArray *)paths
658{
659    NSEnumerator *enumerator = [paths objectEnumerator];
660    NSString *path;
661
662    while ((path = [enumerator nextObject]) != nil) {
663        NSString *MIMEType = WKGetMIMETypeForExtension([path pathExtension]);
664        if (MIMETypeRegistry::isSupportedImageResourceMIMEType(MIMEType))
665            return YES;
666    }
667
668    return NO;
669}
670
671- (WebDataSource *)_dataSource
672{
673    return _private->dataSource;
674}
675
676- (WebView *)_webView
677{
678    return [_private->dataSource _webView];
679}
680
681- (WebFrameView *)_frameView
682{
683    return [[_private->dataSource webFrame] frameView];
684}
685
686- (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths
687{
688    DOMDocumentFragment *fragment;
689    NSEnumerator *enumerator = [paths objectEnumerator];
690    NSMutableArray *domNodes = [[NSMutableArray alloc] init];
691    NSString *path;
692
693    while ((path = [enumerator nextObject]) != nil) {
694        // Non-image file types; _web_userVisibleString is appropriate here because this will
695        // be pasted as visible text.
696        NSString *url = [[[NSURL fileURLWithPath:path] _webkit_canonicalize] _web_userVisibleString];
697        [domNodes addObject:[[[self _frame] DOMDocument] createTextNode: url]];
698    }
699
700    fragment = [[self _frame] _documentFragmentWithNodesAsParagraphs:domNodes];
701
702    [domNodes release];
703
704    return [fragment firstChild] != nil ? fragment : nil;
705}
706
707+ (NSArray *)_excludedElementsForAttributedStringConversion
708{
709    static NSArray *elements = nil;
710    if (elements == nil) {
711        elements = [[NSArray alloc] initWithObjects:
712            // Omit style since we want style to be inline so the fragment can be easily inserted.
713            @"style",
714            // Omit xml so the result is not XHTML.
715            @"xml",
716            // Omit tags that will get stripped when converted to a fragment anyway.
717            @"doctype", @"html", @"head", @"body",
718            // Omit deprecated tags.
719            @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u",
720            // Omit object so no file attachments are part of the fragment.
721            @"object", nil];
722        CFRetain(elements);
723    }
724    return elements;
725}
726
727static NSURL* uniqueURLWithRelativePart(NSString *relativePart)
728{
729    CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault);
730    NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef);
731    CFRelease(UUIDRef);
732    NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/%@", WebDataProtocolScheme, UUIDString, relativePart]];
733    CFRelease(UUIDString);
734
735    return URL;
736}
737
738- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard
739                                               inContext:(DOMRange *)context
740                                          allowPlainText:(BOOL)allowPlainText
741{
742    NSArray *types = [pasteboard types];
743    DOMDocumentFragment *fragment = nil;
744
745    if ([types containsObject:WebArchivePboardType] &&
746        (fragment = [self _documentFragmentFromPasteboard:pasteboard
747                                                  forType:WebArchivePboardType
748                                                inContext:context
749                                             subresources:0]))
750        return fragment;
751
752    if ([types containsObject:NSFilenamesPboardType] &&
753        (fragment = [self _documentFragmentFromPasteboard:pasteboard
754                                                  forType:NSFilenamesPboardType
755                                                inContext:context
756                                             subresources:0]))
757        return fragment;
758
759    if ([types containsObject:NSHTMLPboardType] &&
760        (fragment = [self _documentFragmentFromPasteboard:pasteboard
761                                                  forType:NSHTMLPboardType
762                                                inContext:context
763                                             subresources:0]))
764        return fragment;
765
766    if ([types containsObject:NSRTFDPboardType] &&
767        (fragment = [self _documentFragmentFromPasteboard:pasteboard
768                                                  forType:NSRTFDPboardType
769                                                inContext:context
770                                             subresources:0]))
771        return fragment;
772
773    if ([types containsObject:NSRTFPboardType] &&
774        (fragment = [self _documentFragmentFromPasteboard:pasteboard
775                                                  forType:NSRTFPboardType
776                                                inContext:context
777                                             subresources:0]))
778        return fragment;
779
780    if ([types containsObject:NSTIFFPboardType] &&
781        (fragment = [self _documentFragmentFromPasteboard:pasteboard
782                                                  forType:NSTIFFPboardType
783                                                inContext:context
784                                             subresources:0]))
785        return fragment;
786
787    if ([types containsObject:NSPDFPboardType] &&
788        (fragment = [self _documentFragmentFromPasteboard:pasteboard
789                                                  forType:NSPDFPboardType
790                                                inContext:context
791                                             subresources:0]))
792        return fragment;
793
794    if ([types containsObject:(NSString*)kUTTypePNG] &&
795        (fragment = [self _documentFragmentFromPasteboard:pasteboard
796                                                  forType:(NSString*)kUTTypePNG
797                                                inContext:context
798                                             subresources:0]))
799        return fragment;
800
801    if ([types containsObject:NSURLPboardType] &&
802        (fragment = [self _documentFragmentFromPasteboard:pasteboard
803                                                  forType:NSURLPboardType
804                                                inContext:context
805                                             subresources:0]))
806        return fragment;
807
808    if (allowPlainText && [types containsObject:NSStringPboardType] &&
809        (fragment = [self _documentFragmentFromPasteboard:pasteboard
810                                                  forType:NSStringPboardType
811                                                inContext:context
812                                             subresources:0])) {
813        return fragment;
814    }
815
816    return nil;
817}
818
819- (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard
820{
821    NSArray *types = [pasteboard types];
822
823    if ([types containsObject:NSStringPboardType])
824        return [[pasteboard stringForType:NSStringPboardType] precomposedStringWithCanonicalMapping];
825
826    NSAttributedString *attributedString = nil;
827    NSString *string;
828
829    if ([types containsObject:NSRTFDPboardType])
830        attributedString = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
831    if (attributedString == nil && [types containsObject:NSRTFPboardType])
832        attributedString = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
833    if (attributedString != nil) {
834        string = [[attributedString string] copy];
835        [attributedString release];
836        return [string autorelease];
837    }
838
839    if ([types containsObject:NSFilenamesPboardType]) {
840        string = [[pasteboard propertyListForType:NSFilenamesPboardType] componentsJoinedByString:@"\n"];
841        if (string != nil)
842            return string;
843    }
844
845    NSURL *URL;
846
847    if ((URL = [NSURL URLFromPasteboard:pasteboard])) {
848        string = [URL _web_userVisibleString];
849        if ([string length] > 0)
850            return string;
851    }
852
853    return nil;
854}
855
856- (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText
857{
858    WebView *webView = [[self _webView] retain];
859    [webView _setInsertionPasteboard:pasteboard];
860
861    DOMRange *range = [self _selectedRange];
862    Frame* coreFrame = core([self _frame]);
863
864#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
865    DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText];
866    if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:range givenAction:WebViewInsertActionPasted])
867        coreFrame->editor().pasteAsFragment(core(fragment), [self _canSmartReplaceWithPasteboard:pasteboard], false);
868#else
869    // Mail is ignoring the frament passed to the delegate and creates a new one.
870    // We want to avoid creating the fragment twice.
871    if (applicationIsAppleMail()) {
872        if ([self _shouldInsertFragment:nil replacingDOMRange:range givenAction:WebViewInsertActionPasted]) {
873            DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText];
874            if (fragment)
875                coreFrame->editor().pasteAsFragment(core(fragment), [self _canSmartReplaceWithPasteboard:pasteboard], false);
876        }
877    } else {
878        DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText];
879        if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:range givenAction:WebViewInsertActionPasted])
880            coreFrame->editor().pasteAsFragment(core(fragment), [self _canSmartReplaceWithPasteboard:pasteboard], false);
881    }
882#endif
883    [webView _setInsertionPasteboard:nil];
884    [webView release];
885}
886
887- (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard
888{
889    WebView *webView = [[self _webView] retain];
890    [webView _setInsertionPasteboard:pasteboard];
891
892    NSString *text = [self _plainTextFromPasteboard:pasteboard];
893    if ([self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionPasted])
894        [[self _frame] _replaceSelectionWithText:text selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]];
895
896    [webView _setInsertionPasteboard:nil];
897    [webView release];
898}
899
900// This method is needed to support Mac OS X services.
901- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard
902{
903    Frame* coreFrame = core([self _frame]);
904    if (!coreFrame)
905        return NO;
906    if (coreFrame->selection()->isContentRichlyEditable())
907        [self _pasteWithPasteboard:pasteboard allowPlainText:YES];
908    else
909        [self _pasteAsPlainTextWithPasteboard:pasteboard];
910    return YES;
911}
912
913- (void)_removeMouseMovedObserverUnconditionally
914{
915    if (!_private || !_private->observingMouseMovedNotifications)
916        return;
917
918    [[NSNotificationCenter defaultCenter] removeObserver:self name:WKMouseMovedNotification() object:nil];
919    _private->observingMouseMovedNotifications = false;
920}
921
922- (void)_removeSuperviewObservers
923{
924    if (!_private || !_private->observingSuperviewNotifications)
925        return;
926
927    NSView *superview = [self superview];
928    if (!superview || ![self window])
929        return;
930
931    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
932    [notificationCenter removeObserver:self name:NSViewFrameDidChangeNotification object:superview];
933    [notificationCenter removeObserver:self name:NSViewBoundsDidChangeNotification object:superview];
934
935    _private->observingSuperviewNotifications = false;
936}
937
938- (void)_removeWindowObservers
939{
940    if (!_private->observingWindowNotifications)
941        return;
942
943    NSWindow *window = [self window];
944    if (!window)
945        return;
946
947    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
948    [notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil];
949    [notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification object:nil];
950    [notificationCenter removeObserver:self name:WKWindowWillOrderOnScreenNotification() object:window];
951    [notificationCenter removeObserver:self name:WKWindowWillOrderOffScreenNotification() object:window];
952    [notificationCenter removeObserver:self name:NSWindowWillCloseNotification object:window];
953
954    _private->observingWindowNotifications = false;
955}
956
957- (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
958{
959    WebView *webView = [self _webView];
960    DOMNode *child = [fragment firstChild];
961    if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]])
962        return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action];
963    return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action];
964}
965
966- (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
967{
968    WebView *webView = [self _webView];
969    return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:range givenAction:action];
970}
971
972- (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action
973{
974    return [self _shouldInsertText:text replacingDOMRange:[self _selectedRange] givenAction:action];
975}
976
977- (DOMRange *)_selectedRange
978{
979    Frame* coreFrame = core([self _frame]);
980    return coreFrame ? kit(coreFrame->selection()->toNormalizedRange().get()) : nil;
981}
982
983- (BOOL)_shouldDeleteRange:(DOMRange *)range
984{
985    Frame* coreFrame = core([self _frame]);
986    return coreFrame && coreFrame->editor().shouldDeleteRange(core(range));
987}
988
989- (NSView *)_hitViewForEvent:(NSEvent *)event
990{
991    // Usually, we hack AK's hitTest method to catch all events at the topmost WebHTMLView.
992    // Callers of this method, however, want to query the deepest view instead.
993    forceNSViewHitTest = YES;
994    NSView *hitView = [(NSView *)[[self window] contentView] hitTest:[event locationInWindow]];
995    forceNSViewHitTest = NO;
996    return hitView;
997}
998
999- (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString
1000{
1001    // Put HTML on the pasteboard.
1002    if ([types containsObject:WebArchivePboardType]) {
1003        if (RefPtr<LegacyWebArchive> coreArchive = LegacyWebArchive::createFromSelection(core([self _frame]))) {
1004            if (RetainPtr<CFDataRef> data = coreArchive ? coreArchive->rawDataRepresentation() : 0)
1005                [pasteboard setData:(NSData *)data.get() forType:WebArchivePboardType];
1006        }
1007    }
1008
1009    // Put the attributed string on the pasteboard (RTF/RTFD format).
1010    if ([types containsObject:NSRTFDPboardType]) {
1011        if (attributedString == nil) {
1012            attributedString = [self selectedAttributedString];
1013        }
1014        NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
1015        [pasteboard setData:RTFDData forType:NSRTFDPboardType];
1016    }
1017    if ([types containsObject:NSRTFPboardType]) {
1018        if (!attributedString)
1019            attributedString = [self selectedAttributedString];
1020        if ([attributedString containsAttachments])
1021            attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
1022        NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
1023        [pasteboard setData:RTFData forType:NSRTFPboardType];
1024    }
1025
1026    // Put plain string on the pasteboard.
1027    if ([types containsObject:NSStringPboardType]) {
1028        // Map &nbsp; to a plain old space because this is better for source code, other browsers do it,
1029        // and because HTML forces you to do this any time you want two spaces in a row.
1030        NSMutableString *s = [[self selectedString] mutableCopy];
1031        const unichar NonBreakingSpaceCharacter = 0xA0;
1032        NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&NonBreakingSpaceCharacter length:1];
1033        [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])];
1034        [pasteboard setString:s forType:NSStringPboardType];
1035        [s release];
1036    }
1037
1038    if ([self _canSmartCopyOrDelete] && [types containsObject:WebSmartPastePboardType]) {
1039        [pasteboard setData:nil forType:WebSmartPastePboardType];
1040    }
1041}
1042
1043- (void)_setMouseDownEvent:(NSEvent *)event
1044{
1045    ASSERT(!event || [event type] == NSLeftMouseDown || [event type] == NSRightMouseDown || [event type] == NSOtherMouseDown);
1046
1047    if (event == _private->mouseDownEvent)
1048        return;
1049
1050    [event retain];
1051    [_private->mouseDownEvent release];
1052    _private->mouseDownEvent = event;
1053}
1054
1055- (WebHTMLView *)_topHTMLView
1056{
1057    // FIXME: this can fail if the dataSource is nil, which happens when the WebView is tearing down from the window closing.
1058    WebHTMLView *view = (WebHTMLView *)[[[[_private->dataSource _webView] mainFrame] frameView] documentView];
1059    ASSERT(!view || [view isKindOfClass:[WebHTMLView class]]);
1060    return view;
1061}
1062
1063- (BOOL)_isTopHTMLView
1064{
1065    // FIXME: this should be a cached boolean that doesn't rely on _topHTMLView since that can fail (see _topHTMLView).
1066    return self == [self _topHTMLView];
1067}
1068
1069- (void)_web_setPrintingModeRecursive
1070{
1071    [self _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
1072
1073#ifndef NDEBUG
1074    _private->enumeratingSubviews = YES;
1075#endif
1076
1077    NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init];
1078
1079    [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews];
1080
1081    unsigned count = [descendantWebHTMLViews count];
1082    for (unsigned i = 0; i < count; ++i)
1083        [[descendantWebHTMLViews objectAtIndex:i] _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
1084
1085    [descendantWebHTMLViews release];
1086
1087#ifndef NDEBUG
1088    _private->enumeratingSubviews = NO;
1089#endif
1090}
1091
1092- (void)_web_clearPrintingModeRecursive
1093{
1094    [self _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
1095
1096#ifndef NDEBUG
1097    _private->enumeratingSubviews = YES;
1098#endif
1099
1100    NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init];
1101
1102    [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews];
1103
1104    unsigned count = [descendantWebHTMLViews count];
1105    for (unsigned i = 0; i < count; ++i)
1106        [[descendantWebHTMLViews objectAtIndex:i] _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
1107
1108    [descendantWebHTMLViews release];
1109
1110#ifndef NDEBUG
1111    _private->enumeratingSubviews = NO;
1112#endif
1113}
1114
1115- (void)_web_setPrintingModeRecursiveAndAdjustViewSize
1116{
1117    [self _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
1118
1119#ifndef NDEBUG
1120    _private->enumeratingSubviews = YES;
1121#endif
1122
1123    NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init];
1124
1125    [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews];
1126
1127    unsigned count = [descendantWebHTMLViews count];
1128    for (unsigned i = 0; i < count; ++i)
1129        [[descendantWebHTMLViews objectAtIndex:i] _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
1130
1131    [descendantWebHTMLViews release];
1132
1133#ifndef NDEBUG
1134    _private->enumeratingSubviews = NO;
1135#endif
1136}
1137
1138@end
1139
1140@implementation WebHTMLView (WebPrivate)
1141
1142+ (NSArray *)supportedMIMETypes
1143{
1144    return [WebHTMLRepresentation supportedMIMETypes];
1145}
1146
1147+ (NSArray *)supportedImageMIMETypes
1148{
1149    return [WebHTMLRepresentation supportedImageMIMETypes];
1150}
1151
1152+ (NSArray *)supportedNonImageMIMETypes
1153{
1154    return [WebHTMLRepresentation supportedNonImageMIMETypes];
1155}
1156
1157+ (NSArray *)unsupportedTextMIMETypes
1158{
1159    return [WebHTMLRepresentation unsupportedTextMIMETypes];
1160}
1161
1162+ (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent
1163{
1164    // This is a workaround for: <rdar://problem/2981619> NSResponder_Private should include notification for FlagsChanged
1165    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
1166        location:[[flagsChangedEvent window] convertScreenToBase:[NSEvent mouseLocation]]
1167        modifierFlags:[flagsChangedEvent modifierFlags]
1168        timestamp:[flagsChangedEvent timestamp]
1169        windowNumber:[flagsChangedEvent windowNumber]
1170        context:[flagsChangedEvent context]
1171        eventNumber:0 clickCount:0 pressure:0];
1172
1173    // Pretend it's a mouse move.
1174    [[NSNotificationCenter defaultCenter]
1175        postNotificationName:WKMouseMovedNotification() object:self
1176        userInfo:[NSDictionary dictionaryWithObject:fakeEvent forKey:@"NSEvent"]];
1177}
1178
1179- (id)_bridge
1180{
1181    // This method exists to maintain compatibility with Leopard's Dictionary.app, since it
1182    // calls _bridge to get access to convertNSRangeToDOMRange: and convertDOMRangeToNSRange:.
1183    // Return the WebFrame, which implements the compatibility methods. <rdar://problem/6002160>
1184    return [self _frame];
1185}
1186
1187- (void)_updateMouseoverWithFakeEvent
1188{
1189    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
1190        location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
1191        modifierFlags:[[NSApp currentEvent] modifierFlags]
1192        timestamp:[NSDate timeIntervalSinceReferenceDate]
1193        windowNumber:[[self window] windowNumber]
1194        context:[[NSApp currentEvent] context]
1195        eventNumber:0 clickCount:0 pressure:0];
1196
1197    [self _updateMouseoverWithEvent:fakeEvent];
1198}
1199
1200- (void)_frameOrBoundsChanged
1201{
1202    WebView *webView = [self _webView];
1203    WebDynamicScrollBarsView *scrollView = [[[webView mainFrame] frameView] _scrollView];
1204
1205    NSPoint origin = [[self superview] bounds].origin;
1206    if (!NSEqualPoints(_private->lastScrollPosition, origin) && ![scrollView inProgrammaticScroll]) {
1207        if (Frame* coreFrame = core([self _frame])) {
1208            if (FrameView* coreView = coreFrame->view()) {
1209                _private->inScrollPositionChanged = YES;
1210                coreView->scrollPositionChangedViaPlatformWidget();
1211                _private->inScrollPositionChanged = NO;
1212            }
1213        }
1214
1215        [_private->completionController endRevertingChange:NO moveLeft:NO];
1216
1217        [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[self _frameView]];
1218    }
1219    _private->lastScrollPosition = origin;
1220}
1221
1222- (void)_setAsideSubviews
1223{
1224    ASSERT(!_private->subviewsSetAside);
1225    ASSERT(_private->savedSubviews == nil);
1226    _private->savedSubviews = _subviews;
1227#if USE(ACCELERATED_COMPOSITING)
1228    // We need to keep the layer-hosting view in the subviews, otherwise the layers flash.
1229    if (_private->layerHostingView) {
1230        NSArray* newSubviews = [[NSArray alloc] initWithObjects:_private->layerHostingView, nil];
1231        _subviews = newSubviews;
1232    } else
1233        _subviews = nil;
1234#else
1235    _subviews = nil;
1236#endif
1237    _private->subviewsSetAside = YES;
1238 }
1239
1240 - (void)_restoreSubviews
1241 {
1242    ASSERT(_private->subviewsSetAside);
1243#if USE(ACCELERATED_COMPOSITING)
1244    if (_private->layerHostingView) {
1245        [_subviews release];
1246        _subviews = _private->savedSubviews;
1247    } else {
1248        ASSERT(_subviews == nil);
1249        _subviews = _private->savedSubviews;
1250    }
1251#else
1252    ASSERT(_subviews == nil);
1253    _subviews = _private->savedSubviews;
1254#endif
1255    _private->savedSubviews = nil;
1256    _private->subviewsSetAside = NO;
1257}
1258
1259#ifndef NDEBUG
1260
1261- (void)didAddSubview:(NSView *)subview
1262{
1263    if (_private->enumeratingSubviews)
1264        LOG(View, "A view of class %s was added during subview enumeration for layout or printing mode change. This view might paint without first receiving layout.", object_getClassName([subview class]));
1265}
1266#endif
1267
1268
1269- (void)viewWillDraw
1270{
1271    // On window close we will be called when the datasource is nil, then hit an assert in _topHTMLView
1272    // So check if the dataSource is nil before calling [self _isTopHTMLView], this can be removed
1273    // once the FIXME in _isTopHTMLView is fixed.
1274    if (_private->dataSource && [self _isTopHTMLView])
1275        [self _web_updateLayoutAndStyleIfNeededRecursive];
1276    [super viewWillDraw];
1277}
1278
1279
1280// Don't let AppKit even draw subviews. We take care of that.
1281- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView
1282{
1283    // This helps when we print as part of a larger print process.
1284    // If the WebHTMLView itself is what we're printing, then we will never have to do this.
1285    BOOL wasInPrintingMode = _private->printing;
1286    BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
1287    if (isPrinting) {
1288        if (!wasInPrintingMode)
1289            [self _web_setPrintingModeRecursive];
1290        else
1291            [self _web_updateLayoutAndStyleIfNeededRecursive];
1292    } else if (wasInPrintingMode)
1293        [self _web_clearPrintingModeRecursive];
1294
1295    // There are known cases where -viewWillDraw is not called on all views being drawn.
1296    // See <rdar://problem/6964278> for example. Performing layout at this point prevents us from
1297    // trying to paint without layout (which WebCore now refuses to do, instead bailing out without
1298    // drawing at all), but we may still fail to update any regions dirtied by the layout which are
1299    // not already dirty.
1300    if ([self _needsLayout]) {
1301        NSInteger rectCount;
1302        [self getRectsBeingDrawn:0 count:&rectCount];
1303        if (rectCount) {
1304            LOG_ERROR("View needs layout. Either -viewWillDraw wasn't called or layout was invalidated during the display operation. Performing layout now.");
1305            [self _web_updateLayoutAndStyleIfNeededRecursive];
1306        }
1307    }
1308
1309    [self _setAsideSubviews];
1310    [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView];
1311    [self _restoreSubviews];
1312
1313    if (wasInPrintingMode != isPrinting) {
1314        if (wasInPrintingMode)
1315            [self _web_setPrintingModeRecursive];
1316        else
1317            [self _web_clearPrintingModeRecursive];
1318    }
1319}
1320
1321// Don't let AppKit even draw subviews. We take care of that.
1322- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect
1323{
1324    BOOL needToSetAsideSubviews = !_private->subviewsSetAside;
1325
1326    BOOL wasInPrintingMode = _private->printing;
1327    BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
1328
1329    if (needToSetAsideSubviews) {
1330        // This helps when we print as part of a larger print process.
1331        // If the WebHTMLView itself is what we're printing, then we will never have to do this.
1332        if (isPrinting) {
1333            if (!wasInPrintingMode)
1334                [self _web_setPrintingModeRecursive];
1335            else
1336                [self _web_updateLayoutAndStyleIfNeededRecursive];
1337        } else if (wasInPrintingMode)
1338            [self _web_clearPrintingModeRecursive];
1339
1340
1341        [self _setAsideSubviews];
1342    }
1343
1344    [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect];
1345
1346    if (needToSetAsideSubviews) {
1347        if (wasInPrintingMode != isPrinting) {
1348            if (wasInPrintingMode)
1349                [self _web_setPrintingModeRecursive];
1350            else
1351                [self _web_clearPrintingModeRecursive];
1352        }
1353
1354        [self _restoreSubviews];
1355    }
1356}
1357
1358// Don't let AppKit even draw subviews. We take care of that.
1359- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView
1360{
1361    [self _setAsideSubviews];
1362    [super _recursive:recurse displayRectIgnoringOpacity:displayRect inContext:context topView:topView];
1363    [self _restoreSubviews];
1364}
1365
1366// Don't let AppKit even draw subviews. We take care of that.
1367- (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext CGContext:(CGContextRef)ctx topView:(BOOL)isTopView shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor
1368{
1369    BOOL didSetAsideSubviews = NO;
1370
1371    if (!_private->subviewsSetAside) {
1372        [self _setAsideSubviews];
1373        didSetAsideSubviews = YES;
1374    }
1375
1376    [super _recursive:recurseX displayRectIgnoringOpacity:displayRect inGraphicsContext:graphicsContext CGContext:ctx topView:isTopView shouldChangeFontReferenceColor:shouldChangeFontReferenceColor];
1377
1378    if (didSetAsideSubviews)
1379        [self _restoreSubviews];
1380}
1381
1382- (BOOL)_insideAnotherHTMLView
1383{
1384    return self != [self _topHTMLView];
1385}
1386
1387static BOOL isQuickLookEvent(NSEvent *event)
1388{
1389#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
1390    const int kCGSEventSystemSubtypeHotKeyCombinationReleased = 9;
1391    return [event type] == NSSystemDefined && [event subtype] == kCGSEventSystemSubtypeHotKeyCombinationReleased && [event data1] == 'lkup';
1392#else
1393    return NO;
1394#endif
1395}
1396
1397- (NSView *)hitTest:(NSPoint)point
1398{
1399    // WebHTMLView objects handle all events for objects inside them.
1400    // To get those events, we prevent hit testing from AppKit.
1401
1402    // But there are three exceptions to this:
1403    //   1) For right mouse clicks and control clicks we don't yet have an implementation
1404    //      that works for nested views, so we let the hit testing go through the
1405    //      standard NSView code path (needs to be fixed, see bug 4361618).
1406    //   2) Java depends on doing a hit test inside it's mouse moved handling,
1407    //      so we let the hit testing go through the standard NSView code path
1408    //      when the current event is a mouse move (except when we are calling
1409    //      from _updateMouseoverWithEvent, so we have to use a global,
1410    //      forceWebHTMLViewHitTest, for that)
1411    //   3) The acceptsFirstMouse: and shouldDelayWindowOrderingForEvent: methods
1412    //      both need to figure out which view to check with inside the WebHTMLView.
1413    //      They use a global to change the behavior of hitTest: so they can get the
1414    //      right view. The global is forceNSViewHitTest and the method they use to
1415    //      do the hit testing is _hitViewForEvent:. (But this does not work correctly
1416    //      when there is HTML overlapping the view, see bug 4361626)
1417    //   4) NSAccessibilityHitTest relies on this for checking the cursor position.
1418    //      Our check for that is whether the event is NSFlagsChanged.  This works
1419    //      for VoiceOver's Control-Option-F5 command (move focus to item under cursor)
1420    //      and Dictionary's Command-Control-D (open dictionary popup for item under cursor).
1421    //      This is of course a hack.
1422
1423    if (_private->closed)
1424        return nil;
1425
1426    BOOL captureHitsOnSubviews;
1427    if (forceNSViewHitTest)
1428        captureHitsOnSubviews = NO;
1429    else if (forceWebHTMLViewHitTest)
1430        captureHitsOnSubviews = YES;
1431    else {
1432        // FIXME: Why doesn't this include mouse entered/exited events, or other mouse button events?
1433        NSEvent *event = [[self window] currentEvent];
1434        captureHitsOnSubviews = !([event type] == NSMouseMoved
1435            || [event type] == NSRightMouseDown
1436            || ([event type] == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask) != 0)
1437            || [event type] == NSFlagsChanged
1438            || isQuickLookEvent(event));
1439    }
1440
1441    if (!captureHitsOnSubviews) {
1442        NSView* hitView = [super hitTest:point];
1443#if USE(ACCELERATED_COMPOSITING)
1444        if (_private && hitView == _private->layerHostingView)
1445            hitView = self;
1446#endif
1447        return hitView;
1448    }
1449    if ([[self superview] mouse:point inRect:[self frame]])
1450        return self;
1451    return nil;
1452}
1453
1454- (void)_clearLastHitViewIfSelf
1455{
1456    if (lastHitView == self)
1457        lastHitView = nil;
1458}
1459
1460- (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside
1461{
1462    ASSERT(_private->trackingRectOwner == nil);
1463    _private->trackingRectOwner = owner;
1464    _private->trackingRectUserData = data;
1465    return TRACKING_RECT_TAG;
1466}
1467
1468- (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag
1469{
1470    ASSERT(tag == 0 || tag == TRACKING_RECT_TAG);
1471    ASSERT(_private->trackingRectOwner == nil);
1472    _private->trackingRectOwner = owner;
1473    _private->trackingRectUserData = data;
1474    return TRACKING_RECT_TAG;
1475}
1476
1477- (void)_addTrackingRects:(NSRect *)rects owner:(id)owner userDataList:(void **)userDataList assumeInsideList:(BOOL *)assumeInsideList trackingNums:(NSTrackingRectTag *)trackingNums count:(int)count
1478{
1479    ASSERT(count == 1);
1480    ASSERT(trackingNums[0] == 0 || trackingNums[0] == TRACKING_RECT_TAG);
1481    ASSERT(_private->trackingRectOwner == nil);
1482    _private->trackingRectOwner = owner;
1483    _private->trackingRectUserData = userDataList[0];
1484    trackingNums[0] = TRACKING_RECT_TAG;
1485}
1486
1487- (void)removeTrackingRect:(NSTrackingRectTag)tag
1488{
1489    if (tag == 0)
1490        return;
1491
1492    if (_private && (tag == TRACKING_RECT_TAG)) {
1493        _private->trackingRectOwner = nil;
1494        return;
1495    }
1496
1497    if (_private && (tag == _private->lastToolTipTag)) {
1498        [super removeTrackingRect:tag];
1499        _private->lastToolTipTag = 0;
1500        return;
1501    }
1502
1503    // If any other tracking rect is being removed, we don't know how it was created
1504    // and it's possible there's a leak involved (see 3500217)
1505    ASSERT_NOT_REACHED();
1506}
1507
1508- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count
1509{
1510    int i;
1511    for (i = 0; i < count; ++i) {
1512        int tag = tags[i];
1513        if (tag == 0)
1514            continue;
1515        ASSERT(tag == TRACKING_RECT_TAG);
1516        if (_private != nil) {
1517            _private->trackingRectOwner = nil;
1518        }
1519    }
1520}
1521
1522- (void)_sendToolTipMouseExited
1523{
1524    // Nothing matters except window, trackingNumber, and userData.
1525    NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
1526        location:NSMakePoint(0, 0)
1527        modifierFlags:0
1528        timestamp:0
1529        windowNumber:[[self window] windowNumber]
1530        context:NULL
1531        eventNumber:0
1532        trackingNumber:TRACKING_RECT_TAG
1533        userData:_private->trackingRectUserData];
1534    [_private->trackingRectOwner mouseExited:fakeEvent];
1535}
1536
1537- (void)_sendToolTipMouseEntered
1538{
1539    // Nothing matters except window, trackingNumber, and userData.
1540    NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
1541        location:NSMakePoint(0, 0)
1542        modifierFlags:0
1543        timestamp:0
1544        windowNumber:[[self window] windowNumber]
1545        context:NULL
1546        eventNumber:0
1547        trackingNumber:TRACKING_RECT_TAG
1548        userData:_private->trackingRectUserData];
1549    [_private->trackingRectOwner mouseEntered:fakeEvent];
1550}
1551
1552- (void)_setToolTip:(NSString *)string
1553{
1554    NSString *toolTip = [string length] == 0 ? nil : string;
1555    NSString *oldToolTip = _private->toolTip;
1556    if ((toolTip == nil || oldToolTip == nil) ? toolTip == oldToolTip : [toolTip isEqualToString:oldToolTip]) {
1557        return;
1558    }
1559    if (oldToolTip) {
1560        [self _sendToolTipMouseExited];
1561        [oldToolTip release];
1562    }
1563    _private->toolTip = [toolTip copy];
1564    if (toolTip) {
1565        // See radar 3500217 for why we remove all tooltips rather than just the single one we created.
1566        [self removeAllToolTips];
1567        NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
1568        _private->lastToolTipTag = [self addToolTipRect:wideOpenRect owner:self userData:NULL];
1569        [self _sendToolTipMouseEntered];
1570    }
1571}
1572
1573- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
1574{
1575    return [[_private->toolTip copy] autorelease];
1576}
1577
1578static bool mouseEventIsPartOfClickOrDrag(NSEvent *event)
1579{
1580    switch ([event type]) {
1581        case NSLeftMouseDown:
1582        case NSLeftMouseUp:
1583        case NSLeftMouseDragged:
1584        case NSRightMouseDown:
1585        case NSRightMouseUp:
1586        case NSRightMouseDragged:
1587        case NSOtherMouseDown:
1588        case NSOtherMouseUp:
1589        case NSOtherMouseDragged:
1590            return true;
1591        default:
1592            return false;
1593    }
1594}
1595
1596- (void)_updateMouseoverWithEvent:(NSEvent *)event
1597{
1598    if (_private->closed)
1599        return;
1600
1601    NSView *contentView = [[event window] contentView];
1602    NSPoint locationForHitTest = [[contentView superview] convertPoint:[event locationInWindow] fromView:nil];
1603
1604    forceWebHTMLViewHitTest = YES;
1605    NSView *hitView = [contentView hitTest:locationForHitTest];
1606    forceWebHTMLViewHitTest = NO;
1607
1608    WebHTMLView *view = nil;
1609    if ([hitView isKindOfClass:[WebHTMLView class]])
1610        view = (WebHTMLView *)hitView;
1611
1612    if (view)
1613        [view retain];
1614
1615    if (lastHitView != view && lastHitView && [lastHitView _frame]) {
1616        // If we are moving out of a view (or frame), let's pretend the mouse moved
1617        // all the way out of that view. But we have to account for scrolling, because
1618        // WebCore doesn't understand our clipping.
1619        NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect];
1620        float yScroll = visibleRect.origin.y;
1621        float xScroll = visibleRect.origin.x;
1622
1623        NSEvent *event = [NSEvent mouseEventWithType:NSMouseMoved
1624            location:NSMakePoint(-1 - xScroll, -1 - yScroll)
1625            modifierFlags:[[NSApp currentEvent] modifierFlags]
1626            timestamp:[NSDate timeIntervalSinceReferenceDate]
1627            windowNumber:[[view window] windowNumber]
1628            context:[[NSApp currentEvent] context]
1629            eventNumber:0 clickCount:0 pressure:0];
1630        if (Frame* lastHitCoreFrame = core([lastHitView _frame]))
1631            lastHitCoreFrame->eventHandler()->mouseMoved(event);
1632    }
1633
1634    lastHitView = view;
1635
1636    if (view) {
1637        if (Frame* coreFrame = core([view _frame])) {
1638            // We need to do a full, normal hit test during this mouse event if the page is active or if a mouse
1639            // button is currently pressed. It is possible that neither of those things will be true on Lion and
1640            // newer when legacy scrollbars are enabled, because then WebKit receives mouse events all the time.
1641            // If it is one of those cases where the page is not active and the mouse is not pressed, then we can
1642            // fire a much more restricted and efficient scrollbars-only version of the event.
1643            if ([[self window] isKeyWindow] || mouseEventIsPartOfClickOrDrag(event))
1644                coreFrame->eventHandler()->mouseMoved(event);
1645            else
1646                coreFrame->eventHandler()->passMouseMovedEventToScrollbars(event);
1647        }
1648
1649        [view release];
1650    }
1651}
1652
1653+ (NSArray *)_insertablePasteboardTypes
1654{
1655    static NSArray *types = nil;
1656    if (!types) {
1657        types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPDFPboardType,
1658            NSURLPboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, NSColorPboardType, kUTTypePNG, nil];
1659        CFRetain(types);
1660    }
1661    return types;
1662}
1663
1664+ (NSArray *)_selectionPasteboardTypes
1665{
1666    // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423).
1667    return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
1668}
1669
1670- (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard
1671{
1672    [self setPromisedDragTIFFDataSource:0];
1673}
1674
1675- (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type
1676{
1677    if ([type isEqual:NSRTFDPboardType] && [[pasteboard types] containsObject:WebArchivePboardType]) {
1678        WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]];
1679        [pasteboard _web_writePromisedRTFDFromArchive:archive containsImage:[[pasteboard types] containsObject:NSTIFFPboardType]];
1680        [archive release];
1681    } else if ([type isEqual:NSTIFFPboardType] && [self promisedDragTIFFDataSource]) {
1682        if (Image* image = [self promisedDragTIFFDataSource]->image())
1683            [pasteboard setData:(NSData *)image->getTIFFRepresentation() forType:NSTIFFPboardType];
1684        [self setPromisedDragTIFFDataSource:0];
1685    }
1686}
1687
1688- (void)_handleAutoscrollForMouseDragged:(NSEvent *)event
1689{
1690    [self autoscroll:event];
1691    [self _startAutoscrollTimer:event];
1692}
1693
1694- (WebPluginController *)_pluginController
1695{
1696    return _private->pluginController;
1697}
1698
1699- (void)_layoutForPrinting
1700{
1701    // Set printing mode temporarily so we can adjust the size of the view. This will allow
1702    // AppKit's pagination code to use the correct height for the page content. Leaving printing
1703    // mode on indefinitely would interfere with Mail's printing mechanism (at least), so we just
1704    // turn it off again after adjusting the size.
1705    [self _web_setPrintingModeRecursiveAndAdjustViewSize];
1706    [self _web_clearPrintingModeRecursive];
1707}
1708
1709- (void)_smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)rangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString
1710{
1711    if (!pasteString || !rangeToReplace || ![[self _webView] smartInsertDeleteEnabled]) {
1712        if (beforeString)
1713            *beforeString = nil;
1714        if (afterString)
1715            *afterString = nil;
1716        return;
1717    }
1718
1719    [[self _frame] _smartInsertForString:pasteString replacingRange:rangeToReplace beforeString:beforeString afterString:afterString];
1720}
1721
1722- (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard
1723{
1724    return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType];
1725}
1726
1727- (void)_startAutoscrollTimer:(NSEvent *)triggerEvent
1728{
1729    if (_private->autoscrollTimer == nil) {
1730        _private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL
1731            target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES] retain];
1732        _private->autoscrollTriggerEvent = [triggerEvent retain];
1733    }
1734}
1735
1736// FIXME: _selectionRect is deprecated in favor of selectionRect, which is in protocol WebDocumentSelection.
1737// We can't remove this yet because it's still in use by Mail.
1738- (NSRect)_selectionRect
1739{
1740    return [self selectionRect];
1741}
1742
1743- (void)_stopAutoscrollTimer
1744{
1745    NSTimer *timer = _private->autoscrollTimer;
1746    _private->autoscrollTimer = nil;
1747    [_private->autoscrollTriggerEvent release];
1748    _private->autoscrollTriggerEvent = nil;
1749    [timer invalidate];
1750    [timer release];
1751}
1752
1753- (void)_autoscroll
1754{
1755    // Guarantee that the autoscroll timer is invalidated, even if we don't receive
1756    // a mouse up event.
1757    BOOL isStillDown = CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft);
1758    if (!isStillDown){
1759        [self _stopAutoscrollTimer];
1760        return;
1761    }
1762
1763    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
1764        location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
1765        modifierFlags:[[NSApp currentEvent] modifierFlags]
1766        timestamp:[NSDate timeIntervalSinceReferenceDate]
1767        windowNumber:[[self window] windowNumber]
1768        context:[[NSApp currentEvent] context]
1769        eventNumber:0 clickCount:0 pressure:0];
1770    [self mouseDragged:fakeEvent];
1771}
1772
1773- (BOOL)_canEdit
1774{
1775    Frame* coreFrame = core([self _frame]);
1776    return coreFrame && coreFrame->editor().canEdit();
1777}
1778
1779- (BOOL)_canEditRichly
1780{
1781    Frame* coreFrame = core([self _frame]);
1782    return coreFrame && coreFrame->editor().canEditRichly();
1783}
1784
1785- (BOOL)_canAlterCurrentSelection
1786{
1787    return [self _hasSelectionOrInsertionPoint] && [self _isEditable];
1788}
1789
1790- (BOOL)_hasSelection
1791{
1792    Frame* coreFrame = core([self _frame]);
1793    return coreFrame && coreFrame->selection()->isRange();
1794}
1795
1796- (BOOL)_hasSelectionOrInsertionPoint
1797{
1798    Frame* coreFrame = core([self _frame]);
1799    return coreFrame && coreFrame->selection()->isCaretOrRange();
1800}
1801
1802- (BOOL)_hasInsertionPoint
1803{
1804    Frame* coreFrame = core([self _frame]);
1805    return coreFrame && coreFrame->selection()->isCaret();
1806}
1807
1808- (BOOL)_isEditable
1809{
1810    Frame* coreFrame = core([self _frame]);
1811    return coreFrame && coreFrame->selection()->isContentEditable();
1812}
1813
1814- (BOOL)_transparentBackground
1815{
1816    return _private->transparentBackground;
1817}
1818
1819- (void)_setTransparentBackground:(BOOL)f
1820{
1821    _private->transparentBackground = f;
1822}
1823
1824- (NSImage *)_selectionDraggingImage
1825{
1826    if (![self _hasSelection])
1827        return nil;
1828    NSImage *dragImage = selectionImage(core([self _frame]));
1829    [dragImage _web_dissolveToFraction:WebDragImageAlpha];
1830    return dragImage;
1831}
1832
1833- (NSRect)_selectionDraggingRect
1834{
1835    // Mail currently calls this method. We can eliminate it when Mail no longer calls it.
1836    return [self selectionRect];
1837}
1838
1839- (DOMNode *)_insertOrderedList
1840{
1841    Frame* coreFrame = core([self _frame]);
1842    return coreFrame ? kit(coreFrame->editor().insertOrderedList().get()) : nil;
1843}
1844
1845- (DOMNode *)_insertUnorderedList
1846{
1847    Frame* coreFrame = core([self _frame]);
1848    return coreFrame ? kit(coreFrame->editor().insertUnorderedList().get()) : nil;
1849}
1850
1851- (BOOL)_canIncreaseSelectionListLevel
1852{
1853    Frame* coreFrame = core([self _frame]);
1854    return coreFrame && coreFrame->editor().canIncreaseSelectionListLevel();
1855}
1856
1857- (BOOL)_canDecreaseSelectionListLevel
1858{
1859    Frame* coreFrame = core([self _frame]);
1860    return coreFrame && coreFrame->editor().canDecreaseSelectionListLevel();
1861}
1862
1863- (DOMNode *)_increaseSelectionListLevel
1864{
1865    Frame* coreFrame = core([self _frame]);
1866    return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevel().get()) : nil;
1867}
1868
1869- (DOMNode *)_increaseSelectionListLevelOrdered
1870{
1871    Frame* coreFrame = core([self _frame]);
1872    return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevelOrdered().get()) : nil;
1873}
1874
1875- (DOMNode *)_increaseSelectionListLevelUnordered
1876{
1877    Frame* coreFrame = core([self _frame]);
1878    return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevelUnordered().get()) : nil;
1879}
1880
1881- (void)_decreaseSelectionListLevel
1882{
1883    Frame* coreFrame = core([self _frame]);
1884    if (coreFrame)
1885        coreFrame->editor().decreaseSelectionListLevel();
1886}
1887
1888- (void)_setHighlighter:(id<WebHTMLHighlighter>)highlighter ofType:(NSString*)type
1889{
1890    if (!_private->highlighters)
1891        _private->highlighters = [[NSMutableDictionary alloc] init];
1892    [_private->highlighters setObject:highlighter forKey:type];
1893}
1894
1895- (void)_removeHighlighterOfType:(NSString*)type
1896{
1897    [_private->highlighters removeObjectForKey:type];
1898}
1899
1900- (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard
1901{
1902    ASSERT([self _hasSelection]);
1903    NSArray *types = [self pasteboardTypesForSelection];
1904
1905    // Don't write RTFD to the pasteboard when the copied attributed string has no attachments.
1906    NSAttributedString *attributedString = [self selectedAttributedString];
1907    NSMutableArray *mutableTypes = nil;
1908    if (![attributedString containsAttachments]) {
1909        mutableTypes = [types mutableCopy];
1910        [mutableTypes removeObject:NSRTFDPboardType];
1911        types = mutableTypes;
1912    }
1913
1914    [pasteboard declareTypes:types owner:[self _topHTMLView]];
1915    [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:attributedString];
1916    [mutableTypes release];
1917}
1918
1919- (void)close
1920{
1921    // Check for a nil _private here in case we were created with initWithCoder. In that case, the WebView is just throwing
1922    // out the archived WebHTMLView and recreating a new one if needed. So close doesn't need to do anything in that case.
1923    if (!_private || _private->closed)
1924        return;
1925
1926    _private->closed = YES;
1927
1928    [self _clearLastHitViewIfSelf];
1929    [self _removeMouseMovedObserverUnconditionally];
1930    [self _removeWindowObservers];
1931    [self _removeSuperviewObservers];
1932    [_private->pluginController destroyAllPlugins];
1933    [_private->pluginController setDataSource:nil];
1934    // remove tooltips before clearing _private so removeTrackingRect: will work correctly
1935    [self removeAllToolTips];
1936
1937    if (_private->isInSecureInputState) {
1938        DisableSecureEventInput();
1939        _private->isInSecureInputState = NO;
1940    }
1941
1942    [_private clear];
1943}
1944
1945- (BOOL)_hasHTMLDocument
1946{
1947    Frame* coreFrame = core([self _frame]);
1948    if (!coreFrame)
1949        return NO;
1950    Document* document = coreFrame->document();
1951    return document && document->isHTMLDocument();
1952}
1953
1954- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard
1955                                                 forType:(NSString *)pboardType
1956                                               inContext:(DOMRange *)context
1957                                            subresources:(NSArray **)subresources
1958{
1959    if (pboardType == WebArchivePboardType) {
1960        WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]];
1961        if (subresources)
1962            *subresources = [archive subresources];
1963        DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithArchive:archive];
1964        [archive release];
1965        return fragment;
1966    }
1967    if (pboardType == NSFilenamesPboardType)
1968        return [self _documentFragmentWithPaths:[pasteboard propertyListForType:NSFilenamesPboardType]];
1969
1970    if (pboardType == NSHTMLPboardType) {
1971        NSString *HTMLString = [pasteboard stringForType:NSHTMLPboardType];
1972        // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785.
1973        if ([HTMLString hasPrefix:@"Version:"]) {
1974            NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch];
1975            if (range.location != NSNotFound)
1976                HTMLString = [HTMLString substringFromIndex:range.location];
1977        }
1978        if ([HTMLString length] == 0)
1979            return nil;
1980
1981        return [[self _frame] _documentFragmentWithMarkupString:HTMLString baseURLString:nil];
1982    }
1983
1984    // The _hasHTMLDocument clause here is a workaround for a bug in NSAttributedString: Radar 5052369.
1985    // If we call _documentFromRange on an XML document we'll get "setInnerHTML: method not found".
1986    // FIXME: Remove this once bug 5052369 is fixed.
1987    if ([self _hasHTMLDocument] && (pboardType == NSRTFPboardType || pboardType == NSRTFDPboardType)) {
1988        NSAttributedString *string = nil;
1989        if (pboardType == NSRTFDPboardType)
1990            string = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
1991        if (string == nil)
1992            string = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
1993        if (string == nil)
1994            return nil;
1995
1996        NSDictionary *documentAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
1997            [[self class] _excludedElementsForAttributedStringConversion], NSExcludedElementsDocumentAttribute,
1998            self, @"WebResourceHandler", nil];
1999        NSArray *s;
2000
2001        BOOL wasDeferringCallbacks = [[self _webView] defersCallbacks];
2002        if (!wasDeferringCallbacks)
2003            [[self _webView] setDefersCallbacks:YES];
2004
2005        DOMDocumentFragment *fragment = [string _documentFromRange:NSMakeRange(0, [string length])
2006                                                          document:[[self _frame] DOMDocument]
2007                                                documentAttributes:documentAttributes
2008                                                      subresources:&s];
2009        if (subresources)
2010            *subresources = s;
2011
2012        NSEnumerator *e = [s objectEnumerator];
2013        WebResource *r;
2014        while ((r = [e nextObject]))
2015            [[self _dataSource] addSubresource:r];
2016
2017        if (!wasDeferringCallbacks)
2018            [[self _webView] setDefersCallbacks:NO];
2019
2020        [documentAttributes release];
2021        [string release];
2022        return fragment;
2023    }
2024    if (pboardType == NSTIFFPboardType) {
2025        WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSTIFFPboardType]
2026                                                              URL:uniqueURLWithRelativePart(@"image.tiff")
2027                                                         MIMEType:@"image/tiff"
2028                                                 textEncodingName:nil
2029                                                        frameName:nil];
2030        DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
2031        [resource release];
2032        return fragment;
2033    }
2034    if (pboardType == NSPDFPboardType) {
2035        WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPDFPboardType]
2036                                                              URL:uniqueURLWithRelativePart(@"application.pdf")
2037                                                         MIMEType:@"application/pdf"
2038                                                 textEncodingName:nil
2039                                                        frameName:nil];
2040        DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
2041        [resource release];
2042        return fragment;
2043    }
2044
2045    if ([pboardType isEqualToString:(NSString*)kUTTypePNG]) {
2046        WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:(NSString*)kUTTypePNG]
2047                                                              URL:uniqueURLWithRelativePart(@"image.png")
2048                                                         MIMEType:@"image/png"
2049                                                 textEncodingName:nil
2050                                                        frameName:nil];
2051        DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
2052        [resource release];
2053        return fragment;
2054    }
2055    if (pboardType == NSURLPboardType) {
2056        NSURL *URL = [NSURL URLFromPasteboard:pasteboard];
2057        DOMDocument* document = [[self _frame] DOMDocument];
2058        ASSERT(document);
2059        if (!document)
2060            return nil;
2061        DOMHTMLAnchorElement *anchor = (DOMHTMLAnchorElement *)[document createElement:@"a"];
2062        NSString *URLString = [URL _web_originalDataAsString]; // Original data is ASCII-only, so there is no need to precompose.
2063        if ([URLString length] == 0)
2064            return nil;
2065        NSString *URLTitleString = [[pasteboard stringForType:WebURLNamePboardType] precomposedStringWithCanonicalMapping];
2066        DOMText *text = [document createTextNode:URLTitleString];
2067        [anchor setHref:URLString];
2068        [anchor appendChild:text];
2069        DOMDocumentFragment *fragment = [document createDocumentFragment];
2070        [fragment appendChild:anchor];
2071        return fragment;
2072    }
2073    if (pboardType == NSStringPboardType)
2074        return kit(createFragmentFromText(core(context), [[pasteboard stringForType:NSStringPboardType] precomposedStringWithCanonicalMapping]).get());
2075    return nil;
2076}
2077
2078#if ENABLE(NETSCAPE_PLUGIN_API)
2079- (void)_pauseNullEventsForAllNetscapePlugins
2080{
2081    NSArray *subviews = [self subviews];
2082    unsigned int subviewCount = [subviews count];
2083    unsigned int subviewIndex;
2084
2085    for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) {
2086        NSView *subview = [subviews objectAtIndex:subviewIndex];
2087        if ([subview isKindOfClass:[WebBaseNetscapePluginView class]])
2088            [(WebBaseNetscapePluginView *)subview stopTimers];
2089    }
2090}
2091#endif
2092
2093#if ENABLE(NETSCAPE_PLUGIN_API)
2094- (void)_resumeNullEventsForAllNetscapePlugins
2095{
2096    NSArray *subviews = [self subviews];
2097    unsigned int subviewCount = [subviews count];
2098    unsigned int subviewIndex;
2099
2100    for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) {
2101        NSView *subview = [subviews objectAtIndex:subviewIndex];
2102        if ([subview isKindOfClass:[WebBaseNetscapePluginView class]])
2103            [(WebBaseNetscapePluginView *)subview restartTimers];
2104    }
2105}
2106#endif
2107
2108- (BOOL)_isUsingAcceleratedCompositing
2109{
2110#if USE(ACCELERATED_COMPOSITING)
2111    return _private->layerHostingView != nil;
2112#else
2113    return NO;
2114#endif
2115}
2116
2117- (NSView *)_compositingLayersHostingView
2118{
2119#if USE(ACCELERATED_COMPOSITING)
2120    return _private->layerHostingView;
2121#else
2122    return 0;
2123#endif
2124}
2125
2126- (BOOL)_isInPrintMode
2127{
2128    return _private->printing;
2129}
2130
2131- (BOOL)_beginPrintModeWithMinimumPageWidth:(CGFloat)minimumPageWidth height:(CGFloat)minimumPageHeight maximumPageWidth:(CGFloat)maximumPageWidth
2132{
2133    Frame* frame = core([self _frame]);
2134    if (!frame)
2135        return NO;
2136
2137    if (frame->document() && frame->document()->isFrameSet()) {
2138        minimumPageWidth = 0;
2139        minimumPageHeight = 0;
2140    }
2141
2142    float maximumShrinkRatio = 0;
2143    if (minimumPageWidth > 0.0)
2144        maximumShrinkRatio = maximumPageWidth / minimumPageWidth;
2145
2146    [self _setPrinting:YES minimumPageLogicalWidth:minimumPageWidth logicalHeight:minimumPageHeight originalPageWidth:minimumPageWidth originalPageHeight:minimumPageHeight maximumShrinkRatio:maximumShrinkRatio adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
2147    return YES;
2148}
2149
2150- (BOOL)_beginPrintModeWithPageWidth:(float)pageWidth height:(float)pageHeight shrinkToFit:(BOOL)shrinkToFit
2151{
2152    Frame* frame = core([self _frame]);
2153    if (!frame)
2154        return NO;
2155
2156    Document* document = frame->document();
2157    bool isHorizontal = !document || !document->renderView() || document->renderView()->style()->isHorizontalWritingMode();
2158
2159    float pageLogicalWidth = isHorizontal ? pageWidth : pageHeight;
2160    float pageLogicalHeight = isHorizontal ? pageHeight : pageWidth;
2161    FloatSize minLayoutSize(pageLogicalWidth, pageLogicalHeight);
2162    float maximumShrinkRatio = 1;
2163
2164    // If we are a frameset just print with the layout we have onscreen, otherwise relayout
2165    // according to the page width.
2166    if (shrinkToFit && (!frame->document() || !frame->document()->isFrameSet())) {
2167        minLayoutSize = frame->resizePageRectsKeepingRatio(FloatSize(pageLogicalWidth, pageLogicalHeight), FloatSize(pageLogicalWidth * _WebHTMLViewPrintingMinimumShrinkFactor, pageLogicalHeight * _WebHTMLViewPrintingMinimumShrinkFactor));
2168        maximumShrinkRatio = _WebHTMLViewPrintingMaximumShrinkFactor / _WebHTMLViewPrintingMinimumShrinkFactor;
2169    }
2170
2171    [self _setPrinting:YES minimumPageLogicalWidth:minLayoutSize.width() logicalHeight:minLayoutSize.height() originalPageWidth:pageLogicalWidth originalPageHeight:pageLogicalHeight maximumShrinkRatio:maximumShrinkRatio adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
2172
2173    return YES;
2174}
2175
2176- (void)_endPrintMode
2177{
2178    [self _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
2179}
2180
2181- (BOOL)_isInScreenPaginationMode
2182{
2183    return _private->paginateScreenContent;
2184}
2185
2186- (BOOL)_beginScreenPaginationModeWithPageSize:(CGSize)pageSize shrinkToFit:(BOOL)shrinkToFit
2187{
2188    Frame* frame = core([self _frame]);
2189    if (!frame)
2190        return NO;
2191
2192    Document* document = frame->document();
2193    bool isHorizontal = !document || !document->renderView() || document->renderView()->style()->isHorizontalWritingMode();
2194
2195    float pageLogicalWidth = isHorizontal ? pageSize.width : pageSize.height;
2196    float pageLogicalHeight = isHorizontal ? pageSize.height : pageSize.width;
2197    FloatSize minLayoutSize(pageLogicalWidth, pageLogicalHeight);
2198    float maximumShrinkRatio = 1;
2199
2200    // If we are a frameset just print with the layout we have onscreen, otherwise relayout
2201    // according to the page width.
2202    if (shrinkToFit && (!frame->document() || !frame->document()->isFrameSet())) {
2203        minLayoutSize = frame->resizePageRectsKeepingRatio(FloatSize(pageLogicalWidth, pageLogicalHeight), FloatSize(pageLogicalWidth * _WebHTMLViewPrintingMinimumShrinkFactor, pageLogicalHeight * _WebHTMLViewPrintingMinimumShrinkFactor));
2204        maximumShrinkRatio = _WebHTMLViewPrintingMaximumShrinkFactor / _WebHTMLViewPrintingMinimumShrinkFactor;
2205    }
2206
2207    [self _setPrinting:[self _isInPrintMode] minimumPageLogicalWidth:minLayoutSize.width() logicalHeight:minLayoutSize.height() originalPageWidth:pageLogicalWidth originalPageHeight:pageLogicalHeight maximumShrinkRatio:maximumShrinkRatio adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
2208
2209    return YES;
2210}
2211
2212- (void)_endScreenPaginationMode
2213{
2214    [self _setPrinting:[self _isInPrintMode] minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:NO];
2215}
2216
2217- (CGFloat)_adjustedBottomOfPageWithTop:(CGFloat)top bottom:(CGFloat)bottom limit:(CGFloat)bottomLimit
2218{
2219    Frame* frame = core([self _frame]);
2220    if (!frame)
2221        return bottom;
2222
2223    FrameView* view = frame->view();
2224    if (!view)
2225        return bottom;
2226
2227    float newBottom;
2228    view->adjustPageHeightDeprecated(&newBottom, top, bottom, bottomLimit);
2229
2230#ifdef __LP64__
2231    // If the new bottom is equal to the old bottom (when both are treated as floats), we just return the original
2232    // bottom. This prevents rounding errors that can occur when converting newBottom to a double.
2233    if (fabs(static_cast<float>(bottom) - newBottom) <= std::numeric_limits<float>::epsilon())
2234        return bottom;
2235    else
2236#endif
2237        return newBottom;
2238}
2239
2240@end
2241
2242@implementation NSView (WebHTMLViewFileInternal)
2243
2244- (void)_web_addDescendantWebHTMLViewsToArray:(NSMutableArray *)array
2245{
2246    unsigned count = [_subviews count];
2247    for (unsigned i = 0; i < count; ++i) {
2248        NSView *child = [_subviews objectAtIndex:i];
2249        if ([child isKindOfClass:[WebHTMLView class]])
2250            [array addObject:child];
2251        [child _web_addDescendantWebHTMLViewsToArray:array];
2252    }
2253}
2254
2255@end
2256
2257@implementation NSMutableDictionary (WebHTMLViewFileInternal)
2258
2259- (void)_web_setObjectIfNotNil:(id)object forKey:(id)key
2260{
2261    if (object == nil) {
2262        [self removeObjectForKey:key];
2263    } else {
2264        [self setObject:object forKey:key];
2265    }
2266}
2267
2268@end
2269
2270@implementation WebHTMLView
2271
2272+ (void)initialize
2273{
2274    [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes]
2275                             returnTypes:[[self class] _insertablePasteboardTypes]];
2276    JSC::initializeThreading();
2277    WTF::initializeMainThreadToProcessMainThread();
2278    WebCore::RunLoop::initializeMainRunLoop();
2279    WebCoreObjCFinalizeOnMainThread(self);
2280}
2281
2282- (id)initWithFrame:(NSRect)frame
2283{
2284    self = [super initWithFrame:frame];
2285    if (!self)
2286        return nil;
2287
2288    [self setFocusRingType:NSFocusRingTypeNone];
2289
2290    // Make all drawing go through us instead of subviews.
2291    [self _setDrawsOwnDescendants:YES];
2292
2293    _private = [[WebHTMLViewPrivate alloc] init];
2294
2295    _private->pluginController = [[WebPluginController alloc] initWithDocumentView:self];
2296
2297    return self;
2298}
2299
2300- (void)dealloc
2301{
2302    if (WebCoreObjCScheduleDeallocateOnMainThread([WebHTMLView class], self))
2303        return;
2304
2305    // We can't assert that close has already been called because
2306    // this view can be removed from it's superview, even though
2307    // it could be needed later, so close if needed.
2308    [self close];
2309    [_private release];
2310    _private = nil;
2311    [super dealloc];
2312}
2313
2314- (void)finalize
2315{
2316    ASSERT_MAIN_THREAD();
2317    // We can't assert that close has already been called because
2318    // this view can be removed from it's superview, even though
2319    // it could be needed later, so close if needed.
2320    [self close];
2321    [super finalize];
2322}
2323
2324// Returns YES if the delegate returns YES (so we should do no more work).
2325- (BOOL)callDelegateDoCommandBySelectorIfNeeded:(SEL)selector
2326{
2327    BOOL callerAlreadyCalledDelegate = _private->selectorForDoCommandBySelector == selector;
2328    _private->selectorForDoCommandBySelector = 0;
2329    if (callerAlreadyCalledDelegate)
2330        return NO;
2331    WebView *webView = [self _webView];
2332    return [[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector];
2333}
2334
2335typedef HashMap<SEL, String> SelectorNameMap;
2336
2337// Map selectors into Editor command names.
2338// This is not needed for any selectors that have the same name as the Editor command.
2339static const SelectorNameMap* createSelectorExceptionMap()
2340{
2341    SelectorNameMap* map = new HashMap<SEL, String>;
2342
2343    map->add(@selector(insertNewlineIgnoringFieldEditor:), "InsertNewline");
2344    map->add(@selector(insertParagraphSeparator:), "InsertNewline");
2345    map->add(@selector(insertTabIgnoringFieldEditor:), "InsertTab");
2346    map->add(@selector(pageDown:), "MovePageDown");
2347    map->add(@selector(pageDownAndModifySelection:), "MovePageDownAndModifySelection");
2348    map->add(@selector(pageUp:), "MovePageUp");
2349    map->add(@selector(pageUpAndModifySelection:), "MovePageUpAndModifySelection");
2350
2351    return map;
2352}
2353
2354static String commandNameForSelector(SEL selector)
2355{
2356    // Check the exception map first.
2357    static const SelectorNameMap* exceptionMap = createSelectorExceptionMap();
2358    SelectorNameMap::const_iterator it = exceptionMap->find(selector);
2359    if (it != exceptionMap->end())
2360        return it->value;
2361
2362    // Remove the trailing colon.
2363    // No need to capitalize the command name since Editor command names are
2364    // not case sensitive.
2365    const char* selectorName = sel_getName(selector);
2366    size_t selectorNameLength = strlen(selectorName);
2367    if (selectorNameLength < 2 || selectorName[selectorNameLength - 1] != ':')
2368        return String();
2369    return String(selectorName, selectorNameLength - 1);
2370}
2371
2372- (Editor::Command)coreCommandBySelector:(SEL)selector
2373{
2374    Frame* coreFrame = core([self _frame]);
2375    if (!coreFrame)
2376        return Editor::Command();
2377    return coreFrame->editor().command(commandNameForSelector(selector));
2378}
2379
2380- (Editor::Command)coreCommandByName:(const char*)name
2381{
2382    Frame* coreFrame = core([self _frame]);
2383    if (!coreFrame)
2384        return Editor::Command();
2385    return coreFrame->editor().command(name);
2386}
2387
2388- (void)executeCoreCommandBySelector:(SEL)selector
2389{
2390    if ([self callDelegateDoCommandBySelectorIfNeeded:selector])
2391        return;
2392    [self coreCommandBySelector:selector].execute();
2393}
2394
2395- (void)executeCoreCommandByName:(const char*)name
2396{
2397    [self coreCommandByName:name].execute();
2398}
2399
2400// These commands are forwarded to the Editor object in WebCore.
2401// Ideally we'd do this for all editing commands; more of the code
2402// should be moved from here to there, and more commands should be
2403// added to this list.
2404
2405// FIXME: Maybe we should set things up so that all these share a single method implementation function.
2406// The functions are identical.
2407
2408#define WEBCORE_COMMAND(command) - (void)command:(id)sender { [self executeCoreCommandBySelector:_cmd]; }
2409
2410WEBCORE_COMMAND(alignCenter)
2411WEBCORE_COMMAND(alignJustified)
2412WEBCORE_COMMAND(alignLeft)
2413WEBCORE_COMMAND(alignRight)
2414WEBCORE_COMMAND(copy)
2415WEBCORE_COMMAND(cut)
2416WEBCORE_COMMAND(paste)
2417WEBCORE_COMMAND(delete)
2418WEBCORE_COMMAND(deleteBackward)
2419WEBCORE_COMMAND(deleteBackwardByDecomposingPreviousCharacter)
2420WEBCORE_COMMAND(deleteForward)
2421WEBCORE_COMMAND(deleteToBeginningOfLine)
2422WEBCORE_COMMAND(deleteToBeginningOfParagraph)
2423WEBCORE_COMMAND(deleteToEndOfLine)
2424WEBCORE_COMMAND(deleteToEndOfParagraph)
2425WEBCORE_COMMAND(deleteToMark)
2426WEBCORE_COMMAND(deleteWordBackward)
2427WEBCORE_COMMAND(deleteWordForward)
2428WEBCORE_COMMAND(ignoreSpelling)
2429WEBCORE_COMMAND(indent)
2430WEBCORE_COMMAND(insertBacktab)
2431WEBCORE_COMMAND(insertLineBreak)
2432WEBCORE_COMMAND(insertNewline)
2433WEBCORE_COMMAND(insertNewlineIgnoringFieldEditor)
2434WEBCORE_COMMAND(insertParagraphSeparator)
2435WEBCORE_COMMAND(insertTab)
2436WEBCORE_COMMAND(insertTabIgnoringFieldEditor)
2437WEBCORE_COMMAND(makeTextWritingDirectionLeftToRight)
2438WEBCORE_COMMAND(makeTextWritingDirectionNatural)
2439WEBCORE_COMMAND(makeTextWritingDirectionRightToLeft)
2440WEBCORE_COMMAND(moveBackward)
2441WEBCORE_COMMAND(moveBackwardAndModifySelection)
2442WEBCORE_COMMAND(moveDown)
2443WEBCORE_COMMAND(moveDownAndModifySelection)
2444WEBCORE_COMMAND(moveForward)
2445WEBCORE_COMMAND(moveForwardAndModifySelection)
2446WEBCORE_COMMAND(moveLeft)
2447WEBCORE_COMMAND(moveLeftAndModifySelection)
2448WEBCORE_COMMAND(moveParagraphBackwardAndModifySelection)
2449WEBCORE_COMMAND(moveParagraphForwardAndModifySelection)
2450WEBCORE_COMMAND(moveRight)
2451WEBCORE_COMMAND(moveRightAndModifySelection)
2452WEBCORE_COMMAND(moveToBeginningOfDocument)
2453WEBCORE_COMMAND(moveToBeginningOfDocumentAndModifySelection)
2454WEBCORE_COMMAND(moveToBeginningOfLine)
2455WEBCORE_COMMAND(moveToBeginningOfLineAndModifySelection)
2456WEBCORE_COMMAND(moveToBeginningOfParagraph)
2457WEBCORE_COMMAND(moveToBeginningOfParagraphAndModifySelection)
2458WEBCORE_COMMAND(moveToBeginningOfSentence)
2459WEBCORE_COMMAND(moveToBeginningOfSentenceAndModifySelection)
2460WEBCORE_COMMAND(moveToEndOfDocument)
2461WEBCORE_COMMAND(moveToEndOfDocumentAndModifySelection)
2462WEBCORE_COMMAND(moveToEndOfLine)
2463WEBCORE_COMMAND(moveToEndOfLineAndModifySelection)
2464WEBCORE_COMMAND(moveToEndOfParagraph)
2465WEBCORE_COMMAND(moveToEndOfParagraphAndModifySelection)
2466WEBCORE_COMMAND(moveToEndOfSentence)
2467WEBCORE_COMMAND(moveToEndOfSentenceAndModifySelection)
2468WEBCORE_COMMAND(moveToLeftEndOfLine)
2469WEBCORE_COMMAND(moveToLeftEndOfLineAndModifySelection)
2470WEBCORE_COMMAND(moveToRightEndOfLine)
2471WEBCORE_COMMAND(moveToRightEndOfLineAndModifySelection)
2472WEBCORE_COMMAND(moveUp)
2473WEBCORE_COMMAND(moveUpAndModifySelection)
2474WEBCORE_COMMAND(moveWordBackward)
2475WEBCORE_COMMAND(moveWordBackwardAndModifySelection)
2476WEBCORE_COMMAND(moveWordForward)
2477WEBCORE_COMMAND(moveWordForwardAndModifySelection)
2478WEBCORE_COMMAND(moveWordLeft)
2479WEBCORE_COMMAND(moveWordLeftAndModifySelection)
2480WEBCORE_COMMAND(moveWordRight)
2481WEBCORE_COMMAND(moveWordRightAndModifySelection)
2482WEBCORE_COMMAND(outdent)
2483WEBCORE_COMMAND(overWrite)
2484WEBCORE_COMMAND(pageDown)
2485WEBCORE_COMMAND(pageDownAndModifySelection)
2486WEBCORE_COMMAND(pageUp)
2487WEBCORE_COMMAND(pageUpAndModifySelection)
2488WEBCORE_COMMAND(pasteAsPlainText)
2489WEBCORE_COMMAND(selectAll)
2490WEBCORE_COMMAND(selectLine)
2491WEBCORE_COMMAND(selectParagraph)
2492WEBCORE_COMMAND(selectSentence)
2493WEBCORE_COMMAND(selectToMark)
2494WEBCORE_COMMAND(selectWord)
2495WEBCORE_COMMAND(setMark)
2496WEBCORE_COMMAND(subscript)
2497WEBCORE_COMMAND(superscript)
2498WEBCORE_COMMAND(swapWithMark)
2499WEBCORE_COMMAND(transpose)
2500WEBCORE_COMMAND(underline)
2501WEBCORE_COMMAND(unscript)
2502WEBCORE_COMMAND(yank)
2503WEBCORE_COMMAND(yankAndSelect)
2504
2505#undef WEBCORE_COMMAND
2506
2507#define COMMAND_PROLOGUE if ([self callDelegateDoCommandBySelectorIfNeeded:_cmd]) return;
2508
2509- (IBAction)takeFindStringFromSelection:(id)sender
2510{
2511    COMMAND_PROLOGUE
2512
2513    if (![self _hasSelection]) {
2514        NSBeep();
2515        return;
2516    }
2517
2518    [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self];
2519}
2520
2521// This method is needed to support Mac OS X services.
2522- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types
2523{
2524    [pasteboard declareTypes:types owner:[self _topHTMLView]];
2525    [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard];
2526    return YES;
2527}
2528
2529- (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
2530{
2531    BOOL isSendTypeOK = !sendType || ([[self pasteboardTypesForSelection] containsObject:sendType] && [self _hasSelection]);
2532    BOOL isReturnTypeOK = NO;
2533    if (!returnType)
2534        isReturnTypeOK = YES;
2535    else if ([[[self class] _insertablePasteboardTypes] containsObject:returnType] && [self _isEditable]) {
2536        // We can insert strings in any editable context.  We can insert other types, like images, only in rich edit contexts.
2537        isReturnTypeOK = [returnType isEqualToString:NSStringPboardType] || [self _canEditRichly];
2538    }
2539    if (isSendTypeOK && isReturnTypeOK)
2540        return self;
2541    return [[self nextResponder] validRequestorForSendType:sendType returnType:returnType];
2542}
2543
2544// jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari
2545// was using the old jumpToSelection selector in its menu. Newer versions of Safari will use the
2546// selector centerSelectionInVisibleArea. We'll leave the old selector in place for two reasons:
2547// (1) Compatibility between older Safari and newer WebKit; (2) other WebKit-based applications
2548// might be using the selector, and we don't want to break them.
2549- (void)jumpToSelection:(id)sender
2550{
2551    COMMAND_PROLOGUE
2552
2553    if (Frame* coreFrame = core([self _frame]))
2554        coreFrame->selection()->revealSelection(ScrollAlignment::alignCenterAlways);
2555}
2556
2557- (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item
2558{
2559    SEL action = [item action];
2560    RefPtr<Frame> frame = core([self _frame]);
2561
2562    if (!frame)
2563        return NO;
2564
2565    if (Document* doc = frame->document()) {
2566        if (doc->isPluginDocument())
2567            return NO;
2568        if (doc->isImageDocument()) {
2569            if (action == @selector(copy:))
2570                return frame->loader()->isComplete();
2571            return NO;
2572        }
2573    }
2574
2575    if (action == @selector(changeSpelling:)
2576            || action == @selector(_changeSpellingFromMenu:)
2577            || action == @selector(checkSpelling:)
2578            || action == @selector(complete:)
2579            || action == @selector(pasteFont:))
2580        return [self _canEdit];
2581
2582    if (action == @selector(showGuessPanel:)) {
2583        // Match OS X AppKit behavior for post-Tiger. Don't change Tiger behavior.
2584        NSMenuItem *menuItem = (NSMenuItem *)item;
2585        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
2586            BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
2587            [menuItem setTitle:panelShowing
2588                ? UI_STRING_INTERNAL("Hide Spelling and Grammar", "menu item title")
2589                : UI_STRING_INTERNAL("Show Spelling and Grammar", "menu item title")];
2590        }
2591        return [self _canEdit];
2592    }
2593
2594    if (action == @selector(changeBaseWritingDirection:)
2595            || action == @selector(makeBaseWritingDirectionLeftToRight:)
2596            || action == @selector(makeBaseWritingDirectionRightToLeft:)) {
2597        NSWritingDirection writingDirection;
2598
2599        if (action == @selector(changeBaseWritingDirection:)) {
2600            writingDirection = static_cast<NSWritingDirection>([item tag]);
2601            if (writingDirection == NSWritingDirectionNatural)
2602                return NO;
2603        } else if (action == @selector(makeBaseWritingDirectionLeftToRight:))
2604            writingDirection = NSWritingDirectionLeftToRight;
2605        else
2606            writingDirection = NSWritingDirectionRightToLeft;
2607
2608        NSMenuItem *menuItem = (NSMenuItem *)item;
2609        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
2610            String direction = writingDirection == NSWritingDirectionLeftToRight ? "ltr" : "rtl";
2611            [menuItem setState:frame->editor().selectionHasStyle(CSSPropertyDirection, direction)];
2612        }
2613        return [self _canEdit];
2614    }
2615
2616    if (action == @selector(makeBaseWritingDirectionNatural:)) {
2617        NSMenuItem *menuItem = (NSMenuItem *)item;
2618        if ([menuItem isKindOfClass:[NSMenuItem class]])
2619            [menuItem setState:NSOffState];
2620        return NO;
2621    }
2622
2623    if (action == @selector(toggleBaseWritingDirection:)) {
2624        NSMenuItem *menuItem = (NSMenuItem *)item;
2625        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
2626            // Take control of the title of the menu item instead of just checking/unchecking it because
2627            // a check would be ambiguous.
2628            [menuItem setTitle:frame->editor().selectionHasStyle(CSSPropertyDirection, "rtl")
2629                ? UI_STRING_INTERNAL("Left to Right", "Left to Right context menu item")
2630                : UI_STRING_INTERNAL("Right to Left", "Right to Left context menu item")];
2631        }
2632        return [self _canEdit];
2633    }
2634
2635    if (action == @selector(changeAttributes:)
2636            || action == @selector(changeColor:)
2637            || action == @selector(changeFont:))
2638        return [self _canEditRichly];
2639
2640    if (action == @selector(capitalizeWord:)
2641               || action == @selector(lowercaseWord:)
2642               || action == @selector(uppercaseWord:))
2643        return [self _hasSelection] && [self _isEditable];
2644
2645    if (action == @selector(centerSelectionInVisibleArea:)
2646               || action == @selector(jumpToSelection:)
2647               || action == @selector(copyFont:))
2648        return [self _hasSelection] || ([self _isEditable] && [self _hasInsertionPoint]);
2649
2650    if (action == @selector(changeDocumentBackgroundColor:))
2651        return [[self _webView] isEditable] && [self _canEditRichly];
2652
2653    if (action == @selector(_ignoreSpellingFromMenu:)
2654            || action == @selector(_learnSpellingFromMenu:)
2655            || action == @selector(takeFindStringFromSelection:))
2656        return [self _hasSelection];
2657
2658    if (action == @selector(paste:) || action == @selector(pasteAsPlainText:))
2659        return frame && (frame->editor().canDHTMLPaste() || frame->editor().canPaste());
2660
2661    if (action == @selector(pasteAsRichText:))
2662        return frame && (frame->editor().canDHTMLPaste()
2663            || (frame->editor().canPaste() && frame->selection()->isContentRichlyEditable()));
2664
2665    if (action == @selector(performFindPanelAction:))
2666        return NO;
2667
2668    if (action == @selector(_lookUpInDictionaryFromMenu:))
2669        return [self _hasSelection];
2670
2671    if (action == @selector(stopSpeaking:))
2672        return [NSApp isSpeaking];
2673
2674    if (action == @selector(toggleGrammarChecking:)) {
2675        // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must validate
2676        // the selector here because we implement it here, and we must implement it here because the AppKit
2677        // code checks the first responder.
2678        NSMenuItem *menuItem = (NSMenuItem *)item;
2679        if ([menuItem isKindOfClass:[NSMenuItem class]])
2680            [menuItem setState:[self isGrammarCheckingEnabled] ? NSOnState : NSOffState];
2681        return YES;
2682    }
2683
2684    if (action == @selector(orderFrontSubstitutionsPanel:)) {
2685        NSMenuItem *menuItem = (NSMenuItem *)item;
2686        if ([menuItem isKindOfClass:[NSMenuItem class]]) {
2687            BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible];
2688            [menuItem setTitle:panelShowing
2689                ? UI_STRING_INTERNAL("Hide Substitutions", "menu item title")
2690                : UI_STRING_INTERNAL("Show Substitutions", "menu item title")];
2691        }
2692        return [self _canEdit];
2693    }
2694    // FIXME 4799134: WebView is the bottleneck for this logic, but we must validate
2695    // the selector here because we implement it here, and we must implement it here because the AppKit
2696    // code checks the first responder.
2697    if (action == @selector(toggleSmartInsertDelete:)) {
2698        NSMenuItem *menuItem = (NSMenuItem *)item;
2699        if ([menuItem isKindOfClass:[NSMenuItem class]])
2700            [menuItem setState:[self smartInsertDeleteEnabled] ? NSOnState : NSOffState];
2701        return [self _canEdit];
2702    }
2703    if (action == @selector(toggleAutomaticQuoteSubstitution:)) {
2704        NSMenuItem *menuItem = (NSMenuItem *)item;
2705        if ([menuItem isKindOfClass:[NSMenuItem class]])
2706            [menuItem setState:[self isAutomaticQuoteSubstitutionEnabled] ? NSOnState : NSOffState];
2707        return [self _canEdit];
2708    }
2709    if (action == @selector(toggleAutomaticLinkDetection:)) {
2710        NSMenuItem *menuItem = (NSMenuItem *)item;
2711        if ([menuItem isKindOfClass:[NSMenuItem class]])
2712            [menuItem setState:[self isAutomaticLinkDetectionEnabled] ? NSOnState : NSOffState];
2713        return [self _canEdit];
2714    }
2715    if (action == @selector(toggleAutomaticDashSubstitution:)) {
2716        NSMenuItem *menuItem = (NSMenuItem *)item;
2717        if ([menuItem isKindOfClass:[NSMenuItem class]])
2718            [menuItem setState:[self isAutomaticDashSubstitutionEnabled] ? NSOnState : NSOffState];
2719        return [self _canEdit];
2720    }
2721    if (action == @selector(toggleAutomaticTextReplacement:)) {
2722        NSMenuItem *menuItem = (NSMenuItem *)item;
2723        if ([menuItem isKindOfClass:[NSMenuItem class]])
2724            [menuItem setState:[self isAutomaticTextReplacementEnabled] ? NSOnState : NSOffState];
2725        return [self _canEdit];
2726    }
2727    if (action == @selector(toggleAutomaticSpellingCorrection:)) {
2728        NSMenuItem *menuItem = (NSMenuItem *)item;
2729        if ([menuItem isKindOfClass:[NSMenuItem class]])
2730            [menuItem setState:[self isAutomaticSpellingCorrectionEnabled] ? NSOnState : NSOffState];
2731        return [self _canEdit];
2732    }
2733
2734    Editor::Command command = [self coreCommandBySelector:action];
2735    if (command.isSupported()) {
2736        NSMenuItem *menuItem = (NSMenuItem *)item;
2737        if ([menuItem isKindOfClass:[NSMenuItem class]])
2738            [menuItem setState:kit(command.state())];
2739        return command.isEnabled();
2740    }
2741
2742    return YES;
2743}
2744
2745- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
2746{
2747    // This can be called during teardown when _webView is nil. Return NO when this happens, because CallUIDelegateReturningBoolean
2748    // assumes the WebVIew is non-nil.
2749    if (![self _webView])
2750        return NO;
2751    BOOL result = [self validateUserInterfaceItemWithoutDelegate:item];
2752    return CallUIDelegateReturningBoolean(result, [self _webView], @selector(webView:validateUserInterfaceItem:defaultValidation:), item, result);
2753}
2754
2755- (BOOL)acceptsFirstResponder
2756{
2757    // Don't accept first responder when we first click on this view.
2758    // We have to pass the event down through WebCore first to be sure we don't hit a subview.
2759    // Do accept first responder at any other time, for example from keyboard events,
2760    // or from calls back from WebCore once we begin mouse-down event handling.
2761    NSEvent *event = [NSApp currentEvent];
2762    if ([event type] == NSLeftMouseDown
2763            && !_private->handlingMouseDownEvent
2764            && NSPointInRect([event locationInWindow], [self convertRect:[self visibleRect] toView:nil])) {
2765        return NO;
2766    }
2767    return YES;
2768}
2769
2770- (BOOL)maintainsInactiveSelection
2771{
2772    // This method helps to determine whether the WebHTMLView should maintain
2773    // an inactive selection when it's not first responder.
2774    // Traditionally, these views have not maintained such selections,
2775    // clearing them when the view was not first responder. However,
2776    // to fix bugs like this one:
2777    // <rdar://problem/3672088>: "Editable WebViews should maintain a selection even
2778    //                            when they're not firstResponder"
2779    // it was decided to add a switch to act more like an NSTextView.
2780
2781    if ([[self _webView] maintainsInactiveSelection])
2782        return YES;
2783
2784    // Predict the case where we are losing first responder status only to
2785    // gain it back again. Want to keep the selection in that case.
2786    id nextResponder = [[self window] _newFirstResponderAfterResigning];
2787    if ([nextResponder isKindOfClass:[NSScrollView class]]) {
2788        id contentView = [nextResponder contentView];
2789        if (contentView)
2790            nextResponder = contentView;
2791    }
2792    if ([nextResponder isKindOfClass:[NSClipView class]]) {
2793        id documentView = [nextResponder documentView];
2794        if (documentView)
2795            nextResponder = documentView;
2796    }
2797    if (nextResponder == self)
2798        return YES;
2799
2800    Frame* coreFrame = core([self _frame]);
2801    bool selectionIsEditable = coreFrame && coreFrame->selection()->isContentEditable();
2802    bool nextResponderIsInWebView = [nextResponder isKindOfClass:[NSView class]]
2803        && [nextResponder isDescendantOf:[[[self _webView] mainFrame] frameView]];
2804
2805    return selectionIsEditable && nextResponderIsInWebView;
2806}
2807
2808- (void)addMouseMovedObserver
2809{
2810    if (!_private->dataSource || ![self _isTopHTMLView] || _private->observingMouseMovedNotifications)
2811        return;
2812
2813    // Unless the Dashboard asks us to do this for all windows, keep an observer going only for the key window.
2814    if (!([[self window] isKeyWindow]
2815#if ENABLE(DASHBOARD_SUPPORT)
2816            || [[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows]
2817#endif
2818        ))
2819        return;
2820
2821    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mouseMovedNotification:)
2822        name:WKMouseMovedNotification() object:nil];
2823    [self _frameOrBoundsChanged];
2824    _private->observingMouseMovedNotifications = true;
2825}
2826
2827- (void)removeMouseMovedObserver
2828{
2829#if ENABLE(DASHBOARD_SUPPORT)
2830    // Don't remove the observer if we're running the Dashboard.
2831    if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows])
2832        return;
2833#endif
2834
2835#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
2836    // Legacy scrollbars require tracking the mouse at all times.
2837    if (WKRecommendedScrollerStyle() == NSScrollerStyleLegacy)
2838        return;
2839#endif
2840
2841    [[self _webView] _mouseDidMoveOverElement:nil modifierFlags:0];
2842    [self _removeMouseMovedObserverUnconditionally];
2843}
2844
2845- (void)addSuperviewObservers
2846{
2847    if (_private->observingSuperviewNotifications)
2848        return;
2849
2850    NSView *superview = [self superview];
2851    if (!superview || ![self window])
2852        return;
2853
2854    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
2855    [notificationCenter addObserver:self selector:@selector(_frameOrBoundsChanged) name:NSViewFrameDidChangeNotification object:superview];
2856    [notificationCenter addObserver:self selector:@selector(_frameOrBoundsChanged) name:NSViewBoundsDidChangeNotification object:superview];
2857
2858    // In addition to registering for frame/bounds change notifications, call -_frameOrBoundsChanged.
2859    // It will check the current scroll against the previous layout's scroll.  We need to
2860    // do this here to catch the case where the WebView is laid out at one size, removed from its
2861    // window, resized, and inserted into another window.  Our frame/bounds changed notifications
2862    // will not be sent in that situation, since we only watch for changes while in the view hierarchy.
2863    [self _frameOrBoundsChanged];
2864
2865    _private->observingSuperviewNotifications = true;
2866}
2867
2868- (void)addWindowObservers
2869{
2870    if (_private->observingWindowNotifications)
2871        return;
2872
2873    NSWindow *window = [self window];
2874    if (!window)
2875        return;
2876
2877    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
2878    [notificationCenter addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:nil];
2879    [notificationCenter addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:nil];
2880    [notificationCenter addObserver:self selector:@selector(windowWillOrderOnScreen:) name:WKWindowWillOrderOnScreenNotification() object:window];
2881    [notificationCenter addObserver:self selector:@selector(windowWillOrderOffScreen:) name:WKWindowWillOrderOffScreenNotification() object:window];
2882    [notificationCenter addObserver:self selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:window];
2883
2884    _private->observingWindowNotifications = true;
2885}
2886
2887- (void)viewWillMoveToSuperview:(NSView *)newSuperview
2888{
2889    [self _removeSuperviewObservers];
2890}
2891
2892- (void)viewDidMoveToSuperview
2893{
2894    if ([self superview] != nil)
2895        [self addSuperviewObservers];
2896
2897#if USE(ACCELERATED_COMPOSITING)
2898    if ([self superview] && [self _isUsingAcceleratedCompositing]) {
2899        WebView *webView = [self _webView];
2900        if ([webView _postsAcceleratedCompositingNotifications])
2901            [[NSNotificationCenter defaultCenter] postNotificationName:_WebViewDidStartAcceleratedCompositingNotification object:webView userInfo:nil];
2902    }
2903#endif
2904}
2905
2906- (void)viewWillMoveToWindow:(NSWindow *)window
2907{
2908    // Don't do anything if we aren't initialized.  This happens
2909    // when decoding a WebView.  When WebViews are decoded their subviews
2910    // are created by initWithCoder: and so won't be normally
2911    // initialized.  The stub views are discarded by WebView.
2912    if (!_private)
2913        return;
2914
2915    // FIXME: Some of these calls may not work because this view may be already removed from it's superview.
2916    [self _removeMouseMovedObserverUnconditionally];
2917    [self _removeWindowObservers];
2918    [self _removeSuperviewObservers];
2919
2920    // FIXME: This accomplishes the same thing as the call to setCanStartMedia(false) in
2921    // WebView. It would be nice to have a single mechanism instead of two.
2922    [[self _pluginController] stopAllPlugins];
2923}
2924
2925- (void)viewDidMoveToWindow
2926{
2927    // Don't do anything if we aren't initialized.  This happens
2928    // when decoding a WebView.  When WebViews are decoded their subviews
2929    // are created by initWithCoder: and so won't be normally
2930    // initialized.  The stub views are discarded by WebView.
2931    if (!_private || _private->closed)
2932        return;
2933
2934    [self _stopAutoscrollTimer];
2935    if ([self window]) {
2936        _private->lastScrollPosition = [[self superview] bounds].origin;
2937        [self addWindowObservers];
2938        [self addSuperviewObservers];
2939        [self addMouseMovedObserver];
2940
2941        // FIXME: This accomplishes the same thing as the call to setCanStartMedia(true) in
2942        // WebView. It would be nice to have a single mechanism instead of two.
2943        [[self _pluginController] startAllPlugins];
2944
2945        _private->lastScrollPosition = NSZeroPoint;
2946    }
2947}
2948
2949- (void)_web_makePluginSubviewsPerformSelector:(SEL)selector withObject:(id)object
2950{
2951#if ENABLE(NETSCAPE_PLUGIN_API)
2952    // Copy subviews because [self subviews] returns the view's mutable internal array,
2953    // and we must avoid mutating the array while enumerating it.
2954    NSArray *subviews = [[self subviews] copy];
2955
2956    NSEnumerator *enumerator = [subviews objectEnumerator];
2957    WebNetscapePluginView *view;
2958    while ((view = [enumerator nextObject]) != nil)
2959        if ([view isKindOfClass:[WebBaseNetscapePluginView class]])
2960            [view performSelector:selector withObject:object];
2961
2962    [subviews release];
2963#endif
2964}
2965
2966- (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
2967{
2968    [self _web_makePluginSubviewsPerformSelector:@selector(viewWillMoveToHostWindow:) withObject:hostWindow];
2969}
2970
2971- (void)viewDidMoveToHostWindow
2972{
2973    [self _web_makePluginSubviewsPerformSelector:@selector(viewDidMoveToHostWindow) withObject:nil];
2974}
2975
2976
2977- (void)addSubview:(NSView *)view
2978{
2979    [super addSubview:view];
2980
2981    if ([WebPluginController isPlugInView:view])
2982        [[self _pluginController] addPlugin:view];
2983}
2984
2985- (void)willRemoveSubview:(NSView *)subview
2986{
2987#ifndef NDEBUG
2988    // Have to null-check _private, since this can be called via -dealloc when
2989    // cleaning up the the layerHostingView.
2990    if (_private && _private->enumeratingSubviews)
2991        LOG(View, "A view of class %s was removed during subview enumeration for layout or printing mode change. We will still do layout or the printing mode change even though this view is no longer in the view hierarchy.", object_getClassName([subview class]));
2992#endif
2993
2994    if ([WebPluginController isPlugInView:subview])
2995        [[self _pluginController] destroyPlugin:subview];
2996
2997    [super willRemoveSubview:subview];
2998}
2999
3000- (void)reapplyStyles
3001{
3002#ifdef LOG_TIMES
3003    double start = CFAbsoluteTimeGetCurrent();
3004#endif
3005
3006    if (Frame* coreFrame = core([self _frame]))
3007        coreFrame->document()->styleResolverChanged(RecalcStyleImmediately);
3008
3009#ifdef LOG_TIMES
3010    double thisTime = CFAbsoluteTimeGetCurrent() - start;
3011    LOG(Timing, "%s apply style seconds = %f", [self URL], thisTime);
3012#endif
3013}
3014
3015// Do a layout, but set up a new fixed width for the purposes of doing printing layout.
3016// minPageWidth==0 implies a non-printing layout
3017- (void)layoutToMinimumPageWidth:(float)minPageLogicalWidth height:(float)minPageLogicalHeight originalPageWidth:(float)originalPageWidth originalPageHeight:(float)originalPageHeight maximumShrinkRatio:(float)maximumShrinkRatio adjustingViewSize:(BOOL)adjustViewSize
3018{
3019    if (![self _needsLayout])
3020        return;
3021
3022#ifdef LOG_TIMES
3023    double start = CFAbsoluteTimeGetCurrent();
3024#endif
3025
3026    LOG(View, "%@ doing layout", self);
3027
3028    Frame* coreFrame = core([self _frame]);
3029    if (!coreFrame)
3030        return;
3031
3032    if (FrameView* coreView = coreFrame->view()) {
3033        if (minPageLogicalWidth > 0.0) {
3034            FloatSize pageSize(minPageLogicalWidth, minPageLogicalHeight);
3035            FloatSize originalPageSize(originalPageWidth, originalPageHeight);
3036            if (coreFrame->document() && coreFrame->document()->renderView() && !coreFrame->document()->renderView()->style()->isHorizontalWritingMode()) {
3037                pageSize = FloatSize(minPageLogicalHeight, minPageLogicalWidth);
3038                originalPageSize = FloatSize(originalPageHeight, originalPageWidth);
3039            }
3040            coreView->forceLayoutForPagination(pageSize, originalPageSize, maximumShrinkRatio, adjustViewSize ? AdjustViewSize : DoNotAdjustViewSize);
3041        } else {
3042            coreView->forceLayout(!adjustViewSize);
3043            if (adjustViewSize)
3044                coreView->adjustViewSize();
3045        }
3046    }
3047
3048#ifdef LOG_TIMES
3049    double thisTime = CFAbsoluteTimeGetCurrent() - start;
3050    LOG(Timing, "%s layout seconds = %f", [self URL], thisTime);
3051#endif
3052}
3053
3054- (void)layout
3055{
3056    [self layoutToMinimumPageWidth:0 height:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustingViewSize:NO];
3057}
3058
3059// Deliver mouseup events to the DOM for button 2.
3060- (void)rightMouseUp:(NSEvent *)event
3061{
3062    // There's a chance that if we run a nested event loop the event will be released.
3063    // Retaining and then autoreleasing prevents that from causing a problem later here or
3064    // inside AppKit code.
3065    [[event retain] autorelease];
3066
3067    [super rightMouseUp:event];
3068
3069    if (Frame* coreframe = core([self _frame]))
3070        coreframe->eventHandler()->mouseUp(event);
3071}
3072
3073static void setMenuItemTarget(NSMenuItem* menuItem)
3074{
3075    // Don't set the menu item's action to the context menu action forwarder if we already
3076    // have an action.
3077    if ([menuItem action])
3078        return;
3079
3080    [menuItem setTarget:[WebMenuTarget sharedMenuTarget]];
3081    [menuItem setAction:@selector(forwardContextMenuAction:)];
3082}
3083
3084static void setMenuTargets(NSMenu* menu)
3085{
3086    NSInteger itemCount = [menu numberOfItems];
3087    for (NSInteger i = 0; i < itemCount; ++i) {
3088        NSMenuItem *item = [menu itemAtIndex:i];
3089        setMenuItemTarget(item);
3090        if ([item hasSubmenu])
3091            setMenuTargets([item submenu]);
3092    }
3093}
3094
3095- (NSMenu *)menuForEvent:(NSEvent *)event
3096{
3097    // There's a chance that if we run a nested event loop the event will be released.
3098    // Retaining and then autoreleasing prevents that from causing a problem later here or
3099    // inside AppKit code.
3100    [[event retain] autorelease];
3101
3102    [_private->completionController endRevertingChange:NO moveLeft:NO];
3103
3104    RefPtr<Frame> coreFrame = core([self _frame]);
3105    if (!coreFrame)
3106        return nil;
3107
3108    Page* page = coreFrame->page();
3109    if (!page)
3110        return nil;
3111
3112    // Match behavior of other browsers by sending a mousedown event for right clicks.
3113    _private->handlingMouseDownEvent = YES;
3114    page->contextMenuController()->clearContextMenu();
3115    coreFrame->eventHandler()->mouseDown(event);
3116    BOOL handledEvent = coreFrame->eventHandler()->sendContextMenuEvent(PlatformEventFactory::createPlatformMouseEvent(event, page->chrome().platformPageClient()));
3117    _private->handlingMouseDownEvent = NO;
3118
3119    if (!handledEvent)
3120        return nil;
3121
3122    // Re-get page, since it might have gone away during event handling.
3123    page = coreFrame->page();
3124    if (!page)
3125        return nil;
3126
3127    ContextMenu* coreMenu = page->contextMenuController()->contextMenu();
3128    if (!coreMenu)
3129        return nil;
3130
3131    NSArray* menuItems = coreMenu->platformDescription();
3132    if (!menuItems)
3133        return nil;
3134
3135    NSUInteger count = [menuItems count];
3136    if (!count)
3137        return nil;
3138
3139    NSMenu* menu = [[[NSMenu alloc] init] autorelease];
3140    for (NSUInteger i = 0; i < count; i++)
3141        [menu addItem:[menuItems objectAtIndex:i]];
3142    setMenuTargets(menu);
3143
3144    [[WebMenuTarget sharedMenuTarget] setMenuController:page->contextMenuController()];
3145
3146    return menu;
3147}
3148
3149- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag
3150{
3151    return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO];
3152}
3153
3154- (void)clearFocus
3155{
3156    Frame* coreFrame = core([self _frame]);
3157    if (!coreFrame)
3158        return;
3159    Document* document = coreFrame->document();
3160    if (!document)
3161        return;
3162
3163    document->setFocusedElement(0);
3164}
3165
3166- (BOOL)isOpaque
3167{
3168    return [[self _webView] drawsBackground];
3169}
3170
3171- (void)setLayer:(CALayer *)layer
3172{
3173    if (Frame* frame = core([self _frame])) {
3174        if (FrameView* view = frame->view())
3175            view->setPaintsEntireContents(layer);
3176    }
3177
3178    [super setLayer:layer];
3179}
3180
3181#if !LOG_DISABLED
3182- (void)setNeedsDisplay:(BOOL)flag
3183{
3184    LOG(View, "%@ setNeedsDisplay:%@", self, flag ? @"YES" : @"NO");
3185    [super setNeedsDisplay:flag];
3186}
3187#endif
3188
3189- (void)setNeedsDisplayInRect:(NSRect)invalidRect
3190{
3191    if (_private->inScrollPositionChanged) {
3192        // When scrolling, the dirty regions are adjusted for the scroll only
3193        // after NSViewBoundsDidChangeNotification is sent. Translate the invalid
3194        // rect to pre-scrolled coordinates in order to get the right dirty region
3195        // after adjustment. See <rdar://problem/7678927>.
3196        NSPoint origin = [[self superview] bounds].origin;
3197        invalidRect.origin.x -= _private->lastScrollPosition.x - origin.x;
3198        invalidRect.origin.y -= _private->lastScrollPosition.y - origin.y;
3199    }
3200    [super setNeedsDisplayInRect:invalidRect];
3201}
3202
3203- (void)setNeedsLayout: (BOOL)flag
3204{
3205    LOG(View, "%@ setNeedsLayout:%@", self, flag ? @"YES" : @"NO");
3206    if (!flag)
3207        return; // There's no way to say you don't need a layout.
3208    if (Frame* frame = core([self _frame])) {
3209        if (frame->document() && frame->document()->inPageCache())
3210            return;
3211        if (FrameView* view = frame->view())
3212            view->setNeedsLayout();
3213    }
3214}
3215
3216- (void)setNeedsToApplyStyles: (BOOL)flag
3217{
3218    LOG(View, "%@ setNeedsToApplyStyles:%@", self, flag ? @"YES" : @"NO");
3219    if (!flag)
3220        return; // There's no way to say you don't need a style recalc.
3221    if (Frame* frame = core([self _frame])) {
3222        if (frame->document() && frame->document()->inPageCache())
3223            return;
3224        frame->document()->scheduleForcedStyleRecalc();
3225    }
3226}
3227
3228- (void)drawSingleRect:(NSRect)rect
3229{
3230    [NSGraphicsContext saveGraphicsState];
3231    NSRectClip(rect);
3232
3233    ASSERT([[self superview] isKindOfClass:[WebClipView class]]);
3234
3235    [(WebClipView *)[self superview] setAdditionalClip:rect];
3236
3237    @try {
3238        if ([self _transparentBackground]) {
3239            [[NSColor clearColor] set];
3240            NSRectFill (rect);
3241        }
3242
3243        [[self _frame] _drawRect:rect contentsOnly:YES];
3244
3245        WebView *webView = [self _webView];
3246
3247        // This hack is needed for <rdar://problem/5023545>. We can hit a race condition where drawRect will be
3248        // called after the WebView has closed. If the client did not properly close the WebView and set the
3249        // UIDelegate to nil, then the UIDelegate will be stale and this code will crash.
3250        static BOOL version3OrLaterClient = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_QUICKBOOKS_QUIRK);
3251        if (version3OrLaterClient)
3252            [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView convertRect:rect fromView:self]];
3253
3254        if (WebNodeHighlight *currentHighlight = [webView currentNodeHighlight])
3255            [currentHighlight setNeedsUpdateInTargetViewRect:[self convertRect:rect toView:[currentHighlight targetView]]];
3256
3257        [(WebClipView *)[self superview] resetAdditionalClip];
3258
3259        [NSGraphicsContext restoreGraphicsState];
3260    } @catch (NSException *localException) {
3261        [(WebClipView *)[self superview] resetAdditionalClip];
3262        [NSGraphicsContext restoreGraphicsState];
3263        LOG_ERROR("Exception caught while drawing: %@", localException);
3264        [localException raise];
3265    }
3266}
3267
3268- (void)drawRect:(NSRect)rect
3269{
3270    ASSERT_MAIN_THREAD();
3271    LOG(View, "%@ drawing", self);
3272
3273    const NSRect *rects;
3274    NSInteger count;
3275    [self getRectsBeingDrawn:&rects count:&count];
3276
3277    BOOL subviewsWereSetAside = _private->subviewsSetAside;
3278    if (subviewsWereSetAside)
3279        [self _restoreSubviews];
3280
3281#ifdef LOG_TIMES
3282    double start = CFAbsoluteTimeGetCurrent();
3283#endif
3284
3285    // If count == 0 here, use the rect passed in for drawing. This is a workaround for:
3286    // <rdar://problem/3908282> REGRESSION (Mail): No drag image dragging selected text in Blot and Mail
3287    // The reason for the workaround is that this method is called explicitly from the code
3288    // to generate a drag image, and at that time, getRectsBeingDrawn:count: will return a zero count.
3289    const int cRectThreshold = 10;
3290    const float cWastedSpaceThreshold = 0.75f;
3291    BOOL useUnionedRect = (count <= 1) || (count > cRectThreshold);
3292    if (!useUnionedRect) {
3293        // Attempt to guess whether or not we should use the unioned rect or the individual rects.
3294        // We do this by computing the percentage of "wasted space" in the union.  If that wasted space
3295        // is too large, then we will do individual rect painting instead.
3296        float unionPixels = (rect.size.width * rect.size.height);
3297        float singlePixels = 0;
3298        for (int i = 0; i < count; ++i)
3299            singlePixels += rects[i].size.width * rects[i].size.height;
3300        float wastedSpace = 1 - (singlePixels / unionPixels);
3301        if (wastedSpace <= cWastedSpaceThreshold)
3302            useUnionedRect = YES;
3303    }
3304
3305    if (useUnionedRect)
3306        [self drawSingleRect:rect];
3307    else {
3308        for (int i = 0; i < count; ++i)
3309            [self drawSingleRect:rects[i]];
3310    }
3311
3312#ifdef LOG_TIMES
3313    double thisTime = CFAbsoluteTimeGetCurrent() - start;
3314    LOG(Timing, "%s draw seconds = %f", widget->part()->baseURL().URL().latin1(), thisTime);
3315#endif
3316
3317    if (subviewsWereSetAside)
3318        [self _setAsideSubviews];
3319
3320    WebView *webView = [self _webView];
3321
3322#if USE(ACCELERATED_COMPOSITING)
3323    // Only do the synchronization dance if we're drawing into the window, otherwise
3324    // we risk disabling screen updates when no flush is pending.
3325    if ([NSGraphicsContext currentContext] == [[self window] graphicsContext] && [webView _needsOneShotDrawingSynchronization]) {
3326        // Disable screen updates to minimize the chances of the race between the CA
3327        // display link and AppKit drawing causing flashes.
3328        [[self window] disableScreenUpdatesUntilFlush];
3329
3330        // Make sure any layer changes that happened as a result of layout
3331        // via -viewWillDraw are committed.
3332        [CATransaction flush];
3333        [webView _setNeedsOneShotDrawingSynchronization:NO];
3334    }
3335#endif
3336
3337    if (webView)
3338        CallUIDelegate(webView, @selector(webView:didDrawFrame:), [self _frame]);
3339}
3340
3341// Turn off the additional clip while computing our visibleRect.
3342- (NSRect)visibleRect
3343{
3344    if (!([[self superview] isKindOfClass:[WebClipView class]]))
3345        return [super visibleRect];
3346
3347    WebClipView *clipView = (WebClipView *)[self superview];
3348
3349    BOOL hasAdditionalClip = [clipView hasAdditionalClip];
3350    if (!hasAdditionalClip) {
3351        return [super visibleRect];
3352    }
3353
3354    NSRect additionalClip = [clipView additionalClip];
3355    [clipView resetAdditionalClip];
3356    NSRect visibleRect = [super visibleRect];
3357    [clipView setAdditionalClip:additionalClip];
3358    return visibleRect;
3359}
3360
3361- (void)_invalidateGStatesForTree
3362{
3363    // AppKit is in the process of traversing the NSView tree, and is going to send -renewGState to
3364    // descendants, including plug-in views. This can result in calls out to plug-in code and back into
3365    // WebCore via JavaScript, which could normally mutate the NSView tree while it is being traversed.
3366    // Defer those mutations while descendants are being traveresed.
3367    WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates;
3368    [super _invalidateGStatesForTree];
3369}
3370
3371- (BOOL)isFlipped
3372{
3373    return YES;
3374}
3375
3376- (void)windowDidBecomeKey:(NSNotification *)notification
3377{
3378    if (!pthread_main_np()) {
3379        [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
3380        return;
3381    }
3382
3383#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
3384    if (_private->trackingAreaForNonKeyWindow) {
3385        [self removeTrackingArea:_private->trackingAreaForNonKeyWindow];
3386        [_private->trackingAreaForNonKeyWindow release];
3387        _private->trackingAreaForNonKeyWindow = nil;
3388    }
3389#endif
3390
3391    NSWindow *keyWindow = [notification object];
3392
3393    if (keyWindow == [self window]) {
3394        [self addMouseMovedObserver];
3395        [self _updateSecureInputState];
3396    }
3397}
3398
3399- (void)windowDidResignKey:(NSNotification *)notification
3400{
3401    if (!pthread_main_np()) {
3402        [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
3403        return;
3404    }
3405
3406    NSWindow *formerKeyWindow = [notification object];
3407
3408    if (formerKeyWindow == [self window])
3409        [self removeMouseMovedObserver];
3410
3411    if (formerKeyWindow == [self window] || formerKeyWindow == [[self window] attachedSheet]) {
3412        [self _updateSecureInputState];
3413        [_private->completionController endRevertingChange:NO moveLeft:NO];
3414    }
3415
3416#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
3417    if (WKRecommendedScrollerStyle() == NSScrollerStyleLegacy) {
3418        // Legacy style scrollbars have design details that rely on tracking the mouse all the time.
3419        // It's easiest to do this with a tracking area, which we will remove when the window is key
3420        // again.
3421        _private->trackingAreaForNonKeyWindow = [[NSTrackingArea alloc] initWithRect:[self bounds]
3422                                                    options:NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingInVisibleRect | NSTrackingActiveAlways
3423                                                    owner:self
3424                                                    userInfo:nil];
3425        [self addTrackingArea:_private->trackingAreaForNonKeyWindow];
3426    }
3427#endif
3428}
3429
3430- (void)windowWillOrderOnScreen:(NSNotification *)notification
3431{
3432    if (!pthread_main_np()) {
3433        [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
3434        return;
3435    }
3436
3437    // Check if the window is already a key window, which can be the case for NSPopovers.
3438    if ([[self window] isKeyWindow])
3439        [self addMouseMovedObserver];
3440}
3441
3442- (void)windowWillOrderOffScreen:(NSNotification *)notification
3443{
3444    if (!pthread_main_np()) {
3445        [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
3446        return;
3447    }
3448
3449    // When the WebView is in a NSPopover the NSWindowDidResignKeyNotification isn't sent
3450    // unless the parent window loses key. So we need to remove the mouse moved observer.
3451    [self removeMouseMovedObserver];
3452}
3453
3454- (void)windowWillClose:(NSNotification *)notification
3455{
3456    if (!pthread_main_np()) {
3457        [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
3458        return;
3459    }
3460
3461    [_private->completionController endRevertingChange:NO moveLeft:NO];
3462    [[self _pluginController] destroyAllPlugins];
3463}
3464
3465- (void)scrollWheel:(NSEvent *)event
3466{
3467    // There's a chance that responding to this event will run a nested event loop, and
3468    // fetching a new event might release the old one. Retaining and then autoreleasing
3469    // the current event prevents that from causing a problem inside WebKit or AppKit code.
3470    [[event retain] autorelease];
3471
3472    Frame* frame = core([self _frame]);
3473    if (!frame || !frame->eventHandler()->wheelEvent(event))
3474        [super scrollWheel:event];
3475}
3476
3477- (BOOL)_isSelectionEvent:(NSEvent *)event
3478{
3479    NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
3480    return [[[self elementAtPoint:point allowShadowContent:YES] objectForKey:WebElementIsSelectedKey] boolValue];
3481}
3482
3483- (BOOL)_isScrollBarEvent:(NSEvent *)event
3484{
3485    NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
3486    return [[[self elementAtPoint:point allowShadowContent:YES] objectForKey:WebElementIsInScrollBarKey] boolValue];
3487}
3488
3489- (BOOL)acceptsFirstMouse:(NSEvent *)event
3490{
3491    // There's a chance that responding to this event will run a nested event loop, and
3492    // fetching a new event might release the old one. Retaining and then autoreleasing
3493    // the current event prevents that from causing a problem inside WebKit or AppKit code.
3494    [[event retain] autorelease];
3495
3496    NSView *hitView = [self _hitViewForEvent:event];
3497    WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil;
3498
3499#if ENABLE(DASHBOARD_SUPPORT)
3500    if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysAcceptsFirstMouse])
3501        return YES;
3502#endif
3503
3504    if (hitHTMLView) {
3505        bool result = false;
3506        if (Frame* coreFrame = core([hitHTMLView _frame])) {
3507            coreFrame->eventHandler()->setActivationEventNumber([event eventNumber]);
3508            [hitHTMLView _setMouseDownEvent:event];
3509            if ([hitHTMLView _isSelectionEvent:event]) {
3510#if ENABLE(DRAG_SUPPORT)
3511                if (Page* page = coreFrame->page())
3512                    result = coreFrame->eventHandler()->eventMayStartDrag(PlatformEventFactory::createPlatformMouseEvent(event, page->chrome().platformPageClient()));
3513#endif
3514            } else if ([hitHTMLView _isScrollBarEvent:event])
3515                result = true;
3516            [hitHTMLView _setMouseDownEvent:nil];
3517        }
3518        return result;
3519    }
3520    return [hitView acceptsFirstMouse:event];
3521}
3522
3523- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event
3524{
3525    // There's a chance that responding to this event will run a nested event loop, and
3526    // fetching a new event might release the old one. Retaining and then autoreleasing
3527    // the current event prevents that from causing a problem inside WebKit or AppKit code.
3528    [[event retain] autorelease];
3529
3530    NSView *hitView = [self _hitViewForEvent:event];
3531    WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil;
3532    if (hitHTMLView) {
3533        bool result = false;
3534        if ([hitHTMLView _isSelectionEvent:event]) {
3535            [hitHTMLView _setMouseDownEvent:event];
3536#if ENABLE(DRAG_SUPPORT)
3537            if (Frame* coreFrame = core([hitHTMLView _frame])) {
3538                if (Page* page = coreFrame->page())
3539                    result = coreFrame->eventHandler()->eventMayStartDrag(PlatformEventFactory::createPlatformMouseEvent(event, page->chrome().platformPageClient()));
3540            }
3541#endif
3542            [hitHTMLView _setMouseDownEvent:nil];
3543        }
3544        return result;
3545    }
3546    return [hitView shouldDelayWindowOrderingForEvent:event];
3547}
3548
3549- (void)mouseDown:(NSEvent *)event
3550{
3551    // There's a chance that responding to this event will run a nested event loop, and
3552    // fetching a new event might release the old one. Retaining and then autoreleasing
3553    // the current event prevents that from causing a problem inside WebKit or AppKit code.
3554    [[event retain] autorelease];
3555
3556    RetainPtr<WebHTMLView> protector = self;
3557    if ([[self inputContext] wantsToHandleMouseEvents] && [[self inputContext] handleMouseEvent:event])
3558        return;
3559
3560    _private->handlingMouseDownEvent = YES;
3561
3562    // Record the mouse down position so we can determine drag hysteresis.
3563    [self _setMouseDownEvent:event];
3564
3565    NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3566    if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
3567        goto done;
3568
3569    [_private->completionController endRevertingChange:NO moveLeft:NO];
3570
3571    // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here.
3572    // We don't want to pass them along to KHTML a second time.
3573    if (!([event modifierFlags] & NSControlKeyMask)) {
3574        _private->ignoringMouseDraggedEvents = NO;
3575
3576        // Let WebCore get a chance to deal with the event. This will call back to us
3577        // to start the autoscroll timer if appropriate.
3578        if (Frame* coreframe = core([self _frame]))
3579            coreframe->eventHandler()->mouseDown(event);
3580    }
3581
3582done:
3583    _private->handlingMouseDownEvent = NO;
3584}
3585
3586#if ENABLE(DRAG_SUPPORT)
3587- (void)dragImage:(NSImage *)dragImage
3588               at:(NSPoint)at
3589           offset:(NSSize)offset
3590            event:(NSEvent *)event
3591       pasteboard:(NSPasteboard *)pasteboard
3592           source:(id)source
3593        slideBack:(BOOL)slideBack
3594{
3595    ASSERT(self == [self _topHTMLView]);
3596    [super dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack];
3597}
3598
3599- (void)mouseDragged:(NSEvent *)event
3600{
3601    // There's a chance that responding to this event will run a nested event loop, and
3602    // fetching a new event might release the old one. Retaining and then autoreleasing
3603    // the current event prevents that from causing a problem inside WebKit or AppKit code.
3604    [[event retain] autorelease];
3605
3606    NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3607    if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
3608        return;
3609
3610    [self retain];
3611
3612    if (!_private->ignoringMouseDraggedEvents) {
3613        if (Frame* frame = core([self _frame])) {
3614            if (Page* page = frame->page())
3615                page->mainFrame()->eventHandler()->mouseDragged(event);
3616        }
3617    }
3618
3619    [self release];
3620}
3621
3622- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
3623{
3624    ASSERT(![self _webView] || [self _isTopHTMLView]);
3625
3626    Page* page = core([self _webView]);
3627    if (!page)
3628        return NSDragOperationNone;
3629
3630    return (NSDragOperation)page->dragController()->sourceDragOperation();
3631}
3632
3633- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
3634{
3635    ASSERT(![self _webView] || [self _isTopHTMLView]);
3636
3637    NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint];
3638    NSPoint windowMouseLoc = windowImageLoc;
3639
3640    if (Page* page = core([self _webView])) {
3641        DragController* dragController = page->dragController();
3642        windowMouseLoc = NSMakePoint(windowImageLoc.x + dragController->dragOffset().x(), windowImageLoc.y + dragController->dragOffset().y());
3643        dragController->dragEnded();
3644    }
3645
3646    [[self _frame] _dragSourceEndedAt:windowMouseLoc operation:operation];
3647
3648    // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event.
3649    _private->ignoringMouseDraggedEvents = YES;
3650
3651    // Once the dragging machinery kicks in, we no longer get mouse drags or the up event.
3652    // WebCore expects to get balanced down/up's, so we must fake up a mouseup.
3653    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
3654                                            location:windowMouseLoc
3655                                       modifierFlags:[[NSApp currentEvent] modifierFlags]
3656                                           timestamp:[NSDate timeIntervalSinceReferenceDate]
3657                                        windowNumber:[[self window] windowNumber]
3658                                             context:[[NSApp currentEvent] context]
3659                                         eventNumber:0 clickCount:0 pressure:0];
3660    [self mouseUp:fakeEvent]; // This will also update the mouseover state.
3661}
3662
3663static bool matchesExtensionOrEquivalent(NSString *filename, NSString *extension)
3664{
3665    NSString *extensionAsSuffix = [@"." stringByAppendingString:extension];
3666    return [filename _webkit_hasCaseInsensitiveSuffix:extensionAsSuffix]
3667    || ([extension _webkit_isCaseInsensitiveEqualToString:@"jpeg"]
3668        && [filename _webkit_hasCaseInsensitiveSuffix:@".jpg"]);
3669}
3670
3671- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
3672{
3673    NSFileWrapper *wrapper = nil;
3674    NSURL *draggingImageURL = nil;
3675
3676    if (WebCore::CachedImage* tiffResource = [self promisedDragTIFFDataSource]) {
3677
3678        ResourceBuffer *buffer = static_cast<CachedResource*>(tiffResource)->resourceBuffer();
3679        if (!buffer)
3680            goto noPromisedData;
3681
3682        NSData *data = buffer->createNSData();
3683        NSURLResponse *response = tiffResource->response().nsURLResponse();
3684        draggingImageURL = [response URL];
3685        wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease];
3686        NSString* filename = [response suggestedFilename];
3687        NSString* trueExtension(tiffResource->image()->filenameExtension());
3688        if (!matchesExtensionOrEquivalent(filename, trueExtension))
3689            filename = [[filename stringByAppendingString:@"."] stringByAppendingString:trueExtension];
3690        [wrapper setPreferredFilename:filename];
3691    }
3692
3693noPromisedData:
3694
3695    if (!wrapper) {
3696        ASSERT(![self _webView] || [self _isTopHTMLView]);
3697        Page* page = core([self _webView]);
3698
3699        //If a load occurs midway through a drag, the view may be detached, which gives
3700        //us no ability to get to the original Page, so we cannot access any drag state
3701        //FIXME: is there a way to recover?
3702        if (!page)
3703            return nil;
3704
3705        const KURL& imageURL = page->dragController()->draggingImageURL();
3706        ASSERT(!imageURL.isEmpty());
3707        draggingImageURL = imageURL;
3708
3709        wrapper = [[self _dataSource] _fileWrapperForURL:draggingImageURL];
3710    }
3711
3712    if (wrapper == nil) {
3713        LOG_ERROR("Failed to create image file.");
3714        return nil;
3715    }
3716
3717    // FIXME: Report an error if we fail to create a file.
3718    NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]];
3719    path = [[NSFileManager defaultManager] _webkit_pathWithUniqueFilenameForPath:path];
3720    if (![wrapper writeToFile:path atomically:NO updateFilenames:YES])
3721        LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToFile:atomically:updateFilenames:]");
3722
3723    if (draggingImageURL)
3724        [[NSFileManager defaultManager] _webkit_setMetadataURL:[draggingImageURL absoluteString] referrer:nil atPath:path];
3725
3726    return [NSArray arrayWithObject:[path lastPathComponent]];
3727}
3728#endif
3729
3730- (void)mouseUp:(NSEvent *)event
3731{
3732    // There's a chance that responding to this event will run a nested event loop, and
3733    // fetching a new event might release the old one. Retaining and then autoreleasing
3734    // the current event prevents that from causing a problem inside WebKit or AppKit code.
3735    [[event retain] autorelease];
3736
3737    [self _setMouseDownEvent:nil];
3738
3739    NSInputManager *currentInputManager = [NSInputManager currentInputManager];
3740    if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
3741        return;
3742
3743    [self retain];
3744
3745    [self _stopAutoscrollTimer];
3746    if (Frame* frame = core([self _frame])) {
3747        if (Page* page = frame->page())
3748            page->mainFrame()->eventHandler()->mouseUp(event);
3749    }
3750    [self _updateMouseoverWithFakeEvent];
3751
3752    [self release];
3753}
3754
3755- (void)mouseMovedNotification:(NSNotification *)notification
3756{
3757    [self _updateMouseoverWithEvent:[[notification userInfo] objectForKey:@"NSEvent"]];
3758}
3759
3760// returning YES from this method is the way we tell AppKit that it is ok for this view
3761// to be in the key loop even when "tab to all controls" is not on.
3762- (BOOL)needsPanelToBecomeKey
3763{
3764    return YES;
3765}
3766
3767// Utility function to make sure we don't return anything through the NSTextInput
3768// API when an editable region is not currently focused.
3769static BOOL isTextInput(Frame* coreFrame)
3770{
3771    return coreFrame && !coreFrame->selection()->isNone() && coreFrame->selection()->isContentEditable();
3772}
3773
3774static BOOL isInPasswordField(Frame* coreFrame)
3775{
3776    return coreFrame && coreFrame->selection()->isInPasswordField();
3777}
3778
3779static PassRefPtr<KeyboardEvent> currentKeyboardEvent(Frame* coreFrame)
3780{
3781    NSEvent *event = [NSApp currentEvent];
3782    if (!event)
3783        return 0;
3784
3785    switch ([event type]) {
3786    case NSKeyDown: {
3787        PlatformKeyboardEvent platformEvent = PlatformEventFactory::createPlatformKeyboardEvent(event);
3788        platformEvent.disambiguateKeyDownEvent(PlatformEvent::RawKeyDown);
3789        return KeyboardEvent::create(platformEvent, coreFrame->document()->defaultView());
3790    }
3791    case NSKeyUp:
3792        return KeyboardEvent::create(PlatformEventFactory::createPlatformKeyboardEvent(event), coreFrame->document()->defaultView());
3793    default:
3794        return 0;
3795    }
3796}
3797
3798- (BOOL)becomeFirstResponder
3799{
3800    NSSelectionDirection direction = NSDirectSelection;
3801    if (![[self _webView] _isPerformingProgrammaticFocus])
3802        direction = [[self window] keyViewSelectionDirection];
3803
3804    [self _updateFontPanel];
3805
3806    Frame* frame = core([self _frame]);
3807    if (!frame)
3808        return YES;
3809
3810    BOOL exposeInputContext = isTextInput(frame) && !isInPasswordField(frame);
3811    if (exposeInputContext != _private->exposeInputContext) {
3812        _private->exposeInputContext = exposeInputContext;
3813        [NSApp updateWindows];
3814    }
3815
3816    _private->_forceUpdateSecureInputState = YES;
3817    [self _updateSecureInputState];
3818    _private->_forceUpdateSecureInputState = NO;
3819
3820    // FIXME: Kill ring handling is mostly in WebCore, so this call should also be moved there.
3821    frame->editor().setStartNewKillRingSequence(true);
3822
3823    Page* page = frame->page();
3824    if (!page)
3825        return YES;
3826
3827    if (![[self _webView] _isPerformingProgrammaticFocus])
3828        page->focusController()->setFocusedFrame(frame);
3829
3830    page->focusController()->setFocused(true);
3831
3832    if (direction == NSDirectSelection)
3833        return YES;
3834
3835    if (Document* document = frame->document())
3836        document->setFocusedElement(0);
3837    page->focusController()->setInitialFocus(direction == NSSelectingNext ? FocusDirectionForward : FocusDirectionBackward,
3838                                             currentKeyboardEvent(frame).get());
3839    return YES;
3840}
3841
3842- (BOOL)resignFirstResponder
3843{
3844    BOOL resign = [super resignFirstResponder];
3845    if (resign) {
3846        if (_private->isInSecureInputState) {
3847            DisableSecureEventInput();
3848            _private->isInSecureInputState = NO;
3849        }
3850        [_private->completionController endRevertingChange:NO moveLeft:NO];
3851        Frame* coreFrame = core([self _frame]);
3852        if (!coreFrame)
3853            return resign;
3854        Page* page = coreFrame->page();
3855        if (!page)
3856            return resign;
3857        if (![self maintainsInactiveSelection]) {
3858            [self deselectAll];
3859            if (![[self _webView] _isPerformingProgrammaticFocus])
3860                [self clearFocus];
3861        }
3862
3863        id nextResponder = [[self window] _newFirstResponderAfterResigning];
3864        bool nextResponderIsInWebView = [nextResponder isKindOfClass:[NSView class]]
3865            && [nextResponder isDescendantOf:[[[self _webView] mainFrame] frameView]];
3866        if (!nextResponderIsInWebView)
3867            page->focusController()->setFocused(false);
3868    }
3869    return resign;
3870}
3871
3872- (void)setDataSource:(WebDataSource *)dataSource
3873{
3874    ASSERT(dataSource);
3875    if (_private->dataSource != dataSource) {
3876        ASSERT(!_private->closed);
3877        BOOL hadDataSource = _private->dataSource != nil;
3878
3879        [dataSource retain];
3880        [_private->dataSource release];
3881        _private->dataSource = dataSource;
3882        [_private->pluginController setDataSource:dataSource];
3883
3884        if (!hadDataSource)
3885            [self addMouseMovedObserver];
3886    }
3887}
3888
3889- (void)dataSourceUpdated:(WebDataSource *)dataSource
3890{
3891}
3892
3893// This is an override of an NSControl method that wants to repaint the entire view when the window resigns/becomes
3894// key.  WebHTMLView is an NSControl only because it hosts NSCells that are painted by WebCore's Aqua theme
3895// renderer (and those cells must be hosted by an enclosing NSControl in order to paint properly).
3896- (void)updateCell:(NSCell*)cell
3897{
3898}
3899
3900// Does setNeedsDisplay:NO as a side effect when printing is ending.
3901// pageWidth != 0 implies we will relayout to a new width
3902- (void)_setPrinting:(BOOL)printing minimumPageLogicalWidth:(float)minPageLogicalWidth logicalHeight:(float)minPageLogicalHeight originalPageWidth:(float)originalPageWidth originalPageHeight:(float)originalPageHeight maximumShrinkRatio:(float)maximumShrinkRatio adjustViewSize:(BOOL)adjustViewSize paginateScreenContent:(BOOL)paginateScreenContent
3903{
3904    if (printing == _private->printing && paginateScreenContent == _private->paginateScreenContent)
3905        return;
3906
3907    WebFrame *frame = [self _frame];
3908    NSArray *subframes = [frame childFrames];
3909    unsigned n = [subframes count];
3910    unsigned i;
3911    for (i = 0; i != n; ++i) {
3912        WebFrame *subframe = [subframes objectAtIndex:i];
3913        WebFrameView *frameView = [subframe frameView];
3914        if ([[subframe _dataSource] _isDocumentHTML]) {
3915            [(WebHTMLView *)[frameView documentView] _setPrinting:printing minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:adjustViewSize paginateScreenContent:paginateScreenContent];
3916        }
3917    }
3918
3919    [_private->pageRects release];
3920    _private->pageRects = nil;
3921    _private->printing = printing;
3922    _private->paginateScreenContent = paginateScreenContent;
3923
3924    Frame* coreFrame = core([self _frame]);
3925    if (coreFrame) {
3926        if (FrameView* coreView = coreFrame->view())
3927            coreView->setMediaType(_private->printing ? "print" : "screen");
3928        if (Document* document = coreFrame->document()) {
3929            // In setting printing, we should not validate resources already cached for the document.
3930            // See https://bugs.webkit.org/show_bug.cgi?id=43704
3931            ResourceCacheValidationSuppressor validationSuppressor(document->cachedResourceLoader());
3932
3933            document->setPaginatedForScreen(_private->paginateScreenContent);
3934            document->setPrinting(_private->printing);
3935            document->styleResolverChanged(RecalcStyleImmediately);
3936        }
3937    }
3938
3939    [self setNeedsLayout:YES];
3940    [self layoutToMinimumPageWidth:minPageLogicalWidth height:minPageLogicalHeight originalPageWidth:originalPageWidth originalPageHeight:originalPageHeight maximumShrinkRatio:maximumShrinkRatio adjustingViewSize:adjustViewSize];
3941    if (!printing) {
3942        // Can't do this when starting printing or nested printing won't work, see 3491427.
3943        [self setNeedsDisplay:NO];
3944    }
3945}
3946
3947- (BOOL)canPrintHeadersAndFooters
3948{
3949    return YES;
3950}
3951
3952// This is needed for the case where the webview is embedded in the view that's being printed.
3953// It shouldn't be called when the webview is being printed directly.
3954- (void)adjustPageHeightNew:(CGFloat *)newBottom top:(CGFloat)oldTop bottom:(CGFloat)oldBottom limit:(CGFloat)bottomLimit
3955{
3956    // This helps when we print as part of a larger print process.
3957    // If the WebHTMLView itself is what we're printing, then we will never have to do this.
3958    BOOL wasInPrintingMode = _private->printing;
3959    if (!wasInPrintingMode)
3960        [self _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
3961
3962    *newBottom = [self _adjustedBottomOfPageWithTop:oldTop bottom:oldBottom limit:bottomLimit];
3963
3964    if (!wasInPrintingMode) {
3965        NSPrintOperation *currenPrintOperation = [NSPrintOperation currentOperation];
3966        if (currenPrintOperation)
3967            // delay _setPrinting:NO until back to main loop as this method may get called repeatedly
3968            [self performSelector:@selector(_delayedEndPrintMode:) withObject:currenPrintOperation afterDelay:0];
3969        else
3970            // not sure if this is actually ever invoked, it probably shouldn't be
3971            [self _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
3972    }
3973}
3974
3975- (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation
3976{
3977    bool useViewWidth = true;
3978    Frame* coreFrame = core([self _frame]);
3979    if (coreFrame) {
3980        Document* document = coreFrame->document();
3981        if (document && document->renderView())
3982            useViewWidth = document->renderView()->style()->isHorizontalWritingMode();
3983    }
3984
3985    float viewLogicalWidth = useViewWidth ? NSWidth([self bounds]) : NSHeight([self bounds]);
3986    if (viewLogicalWidth < 1) {
3987        LOG_ERROR("%@ has no logical width when printing", self);
3988        return 1.0f;
3989    }
3990
3991    float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
3992    float maxShrinkToFitScaleFactor = 1.0f / _WebHTMLViewPrintingMaximumShrinkFactor;
3993    float shrinkToFitScaleFactor = (useViewWidth ? [printOperation _web_availablePaperWidth] :  [printOperation _web_availablePaperHeight]) / viewLogicalWidth;
3994    return userScaleFactor * max(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor);
3995}
3996
3997// FIXME 3491344: This is a secret AppKit-internal method that we need to override in order
3998// to get our shrink-to-fit to work with a custom pagination scheme. We can do this better
3999// if AppKit makes it SPI/API.
4000- (CGFloat)_provideTotalScaleFactorForPrintOperation:(NSPrintOperation *)printOperation
4001{
4002    return [self _scaleFactorForPrintOperation:printOperation];
4003}
4004
4005// This is used for Carbon printing. At some point we might want to make this public API.
4006- (void)setPageWidthForPrinting:(float)pageWidth
4007{
4008    [self _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
4009    [self _setPrinting:YES minimumPageLogicalWidth:pageWidth logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:1 adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
4010}
4011
4012- (void)_endPrintModeAndRestoreWindowAutodisplay
4013{
4014    [self _endPrintMode];
4015    [[self window] setAutodisplay:YES];
4016}
4017
4018- (void)_delayedEndPrintMode:(NSPrintOperation *)initiatingOperation
4019{
4020    ASSERT_ARG(initiatingOperation, initiatingOperation != nil);
4021    NSPrintOperation *currentOperation = [NSPrintOperation currentOperation];
4022    if (initiatingOperation == currentOperation) {
4023        // The print operation is still underway. We don't expect this to ever happen, hence the assert, but we're
4024        // being extra paranoid here since the printing code is so fragile. Delay the cleanup
4025        // further.
4026        ASSERT_NOT_REACHED();
4027        [self performSelector:@selector(_delayedEndPrintMode:) withObject:initiatingOperation afterDelay:0];
4028    } else if ([currentOperation view] == self) {
4029        // A new print job has started, but it is printing the same WebHTMLView again. We don't expect
4030        // this to ever happen, hence the assert, but we're being extra paranoid here since the printing code is so
4031        // fragile. Do nothing, because we don't want to break the print job currently in progress, and
4032        // the print job currently in progress is responsible for its own cleanup.
4033        ASSERT_NOT_REACHED();
4034    } else {
4035        // The print job that kicked off this delayed call has finished, and this view is not being
4036        // printed again. We expect that no other print job has started. Since this delayed call wasn't
4037        // cancelled, beginDocument and endDocument must not have been called, and we need to clean up
4038        // the print mode here.
4039        ASSERT(currentOperation == nil);
4040        [self _endPrintModeAndRestoreWindowAutodisplay];
4041    }
4042}
4043
4044// Return the number of pages available for printing
4045- (BOOL)knowsPageRange:(NSRangePointer)range
4046{
4047    // Must do this explicit display here, because otherwise the view might redisplay while the print
4048    // sheet was up, using printer fonts (and looking different).
4049    [self displayIfNeeded];
4050    [[self window] setAutodisplay:NO];
4051
4052    [[self _webView] _adjustPrintingMarginsForHeaderAndFooter];
4053    NSPrintOperation *printOperation = [NSPrintOperation currentOperation];
4054    if (![self _beginPrintModeWithPageWidth:[printOperation _web_availablePaperWidth] height:[printOperation _web_availablePaperHeight] shrinkToFit:YES])
4055        return NO;
4056
4057    // Certain types of errors, including invalid page ranges, can cause beginDocument and
4058    // endDocument to be skipped after we've put ourselves in print mode (see 4145905). In those cases
4059    // we need to get out of print mode without relying on any more callbacks from the printing mechanism.
4060    // If we get as far as beginDocument without trouble, then this delayed request will be cancelled.
4061    // If not cancelled, this delayed call will be invoked in the next pass through the main event loop,
4062    // which is after beginDocument and endDocument would be called.
4063    [self performSelector:@selector(_delayedEndPrintMode:) withObject:printOperation afterDelay:0];
4064
4065    // There is a theoretical chance that someone could do some drawing between here and endDocument,
4066    // if something caused setNeedsDisplay after this point. If so, it's not a big tragedy, because
4067    // you'd simply see the printer fonts on screen. As of this writing, this does not happen with Safari.
4068
4069    range->location = 1;
4070    float totalScaleFactor = [self _scaleFactorForPrintOperation:printOperation];
4071    float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
4072    [_private->pageRects release];
4073    float fullPageWidth = floorf([printOperation _web_availablePaperWidth] / totalScaleFactor);
4074    float fullPageHeight = floorf([printOperation _web_availablePaperHeight] / totalScaleFactor);
4075    WebFrame *frame = [self _frame];
4076    NSArray *newPageRects = [frame _computePageRectsWithPrintScaleFactor:userScaleFactor pageSize:NSMakeSize(fullPageWidth, fullPageHeight)];
4077
4078    // AppKit gets all messed up if you give it a zero-length page count (see 3576334), so if we
4079    // hit that case we'll pass along a degenerate 1 pixel square to print. This will print
4080    // a blank page (with correct-looking header and footer if that option is on), which matches
4081    // the behavior of IE and Camino at least.
4082    if ([newPageRects count] == 0)
4083        newPageRects = [NSArray arrayWithObject:[NSValue valueWithRect:NSMakeRect(0, 0, 1, 1)]];
4084
4085    _private->pageRects = [newPageRects retain];
4086
4087    range->length = [_private->pageRects count];
4088
4089    return YES;
4090}
4091
4092// Return the drawing rectangle for a particular page number
4093- (NSRect)rectForPage:(NSInteger)page
4094{
4095    return [[_private->pageRects objectAtIndex:page - 1] rectValue];
4096}
4097
4098- (void)drawPageBorderWithSize:(NSSize)borderSize
4099{
4100    ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize]));
4101    [[self _webView] _drawHeaderAndFooter];
4102}
4103
4104- (void)beginDocument
4105{
4106    @try {
4107        // From now on we'll get a chance to call _endPrintMode in either beginDocument or
4108        // endDocument, so we can cancel the "just in case" pending call.
4109        [NSObject cancelPreviousPerformRequestsWithTarget:self
4110                                                 selector:@selector(_delayedEndPrintMode:)
4111                                                   object:[NSPrintOperation currentOperation]];
4112        [super beginDocument];
4113    } @catch (NSException *localException) {
4114        // Exception during [super beginDocument] means that endDocument will not get called,
4115        // so we need to clean up our "print mode" here.
4116        [self _endPrintModeAndRestoreWindowAutodisplay];
4117    }
4118}
4119
4120- (void)endDocument
4121{
4122    [super endDocument];
4123    // Note sadly at this point [NSGraphicsContext currentContextDrawingToScreen] is still NO
4124    [self _endPrintModeAndRestoreWindowAutodisplay];
4125}
4126
4127- (void)keyDown:(NSEvent *)event
4128{
4129    // There's a chance that responding to this event will run a nested event loop, and
4130    // fetching a new event might release the old one. Retaining and then autoreleasing
4131    // the current event prevents that from causing a problem inside WebKit or AppKit code.
4132    [[event retain] autorelease];
4133
4134    RetainPtr<WebHTMLView> selfProtector = self;
4135    BOOL eventWasSentToWebCore = (_private->keyDownEvent == event);
4136
4137    BOOL callSuper = NO;
4138
4139    [_private->keyDownEvent release];
4140    _private->keyDownEvent = [event retain];
4141
4142    BOOL completionPopupWasOpen = _private->completionController && [_private->completionController popupWindowIsOpen];
4143    Frame* coreFrame = core([self _frame]);
4144    if (!eventWasSentToWebCore && coreFrame && coreFrame->eventHandler()->keyEvent(event)) {
4145        // WebCore processed a key event, bail on any preexisting complete: UI
4146        if (completionPopupWasOpen)
4147            [_private->completionController endRevertingChange:YES moveLeft:NO];
4148    } else if (!_private->completionController || ![_private->completionController filterKeyDown:event]) {
4149        // Not consumed by complete: popup window
4150        [_private->completionController endRevertingChange:YES moveLeft:NO];
4151        callSuper = YES;
4152    }
4153    if (callSuper)
4154        [super keyDown:event];
4155    else
4156        [NSCursor setHiddenUntilMouseMoves:YES];
4157}
4158
4159- (void)keyUp:(NSEvent *)event
4160{
4161    // There's a chance that responding to this event will run a nested event loop, and
4162    // fetching a new event might release the old one. Retaining and then autoreleasing
4163    // the current event prevents that from causing a problem inside WebKit or AppKit code.
4164    [[event retain] autorelease];
4165
4166    BOOL eventWasSentToWebCore = (_private->keyDownEvent == event);
4167
4168    RetainPtr<WebHTMLView> selfProtector = self;
4169    Frame* coreFrame = core([self _frame]);
4170    if (coreFrame && !eventWasSentToWebCore)
4171        coreFrame->eventHandler()->keyEvent(event);
4172    else
4173        [super keyUp:event];
4174}
4175
4176- (void)flagsChanged:(NSEvent *)event
4177{
4178    // There's a chance that responding to this event will run a nested event loop, and
4179    // fetching a new event might release the old one. Retaining and then autoreleasing
4180    // the current event prevents that from causing a problem inside WebKit or AppKit code.
4181    [[event retain] autorelease];
4182
4183    RetainPtr<WebHTMLView> selfProtector = self;
4184
4185    Frame* coreFrame = core([self _frame]);
4186    unsigned short keyCode = [event keyCode];
4187
4188    // Don't make an event from the num lock and function keys.
4189    if (coreFrame && keyCode != 0 && keyCode != 10 && keyCode != 63) {
4190        coreFrame->eventHandler()->keyEvent(PlatformEventFactory::createPlatformKeyboardEvent(event));
4191        return;
4192    }
4193
4194    [super flagsChanged:event];
4195}
4196
4197- (id)accessibilityAttributeValue:(NSString*)attributeName
4198{
4199    if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) {
4200        id accTree = [[self _frame] accessibilityRoot];
4201        if (accTree)
4202            return [NSArray arrayWithObject:accTree];
4203        return nil;
4204    }
4205    return [super accessibilityAttributeValue:attributeName];
4206}
4207
4208- (id)accessibilityFocusedUIElement
4209{
4210    id accTree = [[self _frame] accessibilityRoot];
4211    if (accTree)
4212        return [accTree accessibilityFocusedUIElement];
4213    return self;
4214}
4215
4216- (id)accessibilityHitTest:(NSPoint)point
4217{
4218    id accTree = [[self _frame] accessibilityRoot];
4219    if (accTree) {
4220        NSPoint windowCoord = [[self window] convertScreenToBase:point];
4221        return [accTree accessibilityHitTest:[self convertPoint:windowCoord fromView:nil]];
4222    }
4223    return self;
4224}
4225
4226- (id)_accessibilityParentForSubview:(NSView *)subview
4227{
4228    id accTree = [[self _frame] accessibilityRoot];
4229    if (!accTree)
4230        return self;
4231    id parent = [accTree _accessibilityParentForSubview:subview];
4232    if (!parent)
4233        return self;
4234    return parent;
4235}
4236
4237- (void)centerSelectionInVisibleArea:(id)sender
4238{
4239    COMMAND_PROLOGUE
4240
4241    if (Frame* coreFrame = core([self _frame]))
4242        coreFrame->selection()->revealSelection(ScrollAlignment::alignCenterAlways);
4243}
4244
4245- (NSData *)_selectionStartFontAttributesAsRTF
4246{
4247    Frame* coreFrame = core([self _frame]);
4248    NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"x"
4249        attributes:coreFrame ? coreFrame->editor().fontAttributesForSelectionStart() : nil];
4250    NSData *data = [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:nil];
4251    [string release];
4252    return data;
4253}
4254
4255- (NSDictionary *)_fontAttributesFromFontPasteboard
4256{
4257    NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
4258    if (fontPasteboard == nil)
4259        return nil;
4260    NSData *data = [fontPasteboard dataForType:NSFontPboardType];
4261    if (data == nil || [data length] == 0)
4262        return nil;
4263    // NSTextView does something more efficient by parsing the attributes only, but that's not available in API.
4264    NSAttributedString *string = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease];
4265    if (string == nil || [string length] == 0)
4266        return nil;
4267    return [string fontAttributesInRange:NSMakeRange(0, 1)];
4268}
4269
4270- (DOMCSSStyleDeclaration *)_emptyStyle
4271{
4272    return [[[self _frame] DOMDocument] createCSSStyleDeclaration];
4273}
4274
4275- (NSString *)_colorAsString:(NSColor *)color
4276{
4277    NSColor *rgbColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace];
4278    // FIXME: If color is non-nil and rgbColor is nil, that means we got some kind
4279    // of fancy color that can't be converted to RGB. Changing that to "transparent"
4280    // might not be great, but it's probably OK.
4281    if (rgbColor == nil)
4282        return @"transparent";
4283    float r = [rgbColor redComponent];
4284    float g = [rgbColor greenComponent];
4285    float b = [rgbColor blueComponent];
4286    float a = [rgbColor alphaComponent];
4287    if (a == 0)
4288        return @"transparent";
4289    if (r == 0 && g == 0 && b == 0 && a == 1)
4290        return @"black";
4291    if (r == 1 && g == 1 && b == 1 && a == 1)
4292        return @"white";
4293    // FIXME: Lots more named colors. Maybe we could use the table in WebCore?
4294    if (a == 1)
4295        return [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", r * 255, g * 255, b * 255];
4296    return [NSString stringWithFormat:@"rgba(%.0f,%.0f,%.0f,%f)", r * 255, g * 255, b * 255, a];
4297}
4298
4299- (NSString *)_shadowAsString:(NSShadow *)shadow
4300{
4301    if (shadow == nil)
4302        return @"none";
4303    NSSize offset = [shadow shadowOffset];
4304    float blurRadius = [shadow shadowBlurRadius];
4305    if (offset.width == 0 && offset.height == 0 && blurRadius == 0)
4306        return @"none";
4307    NSColor *color = [shadow shadowColor];
4308    if (color == nil)
4309        return @"none";
4310    // FIXME: Handle non-integral values here?
4311    if (blurRadius == 0)
4312        return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height];
4313    return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height, blurRadius];
4314}
4315
4316- (DOMCSSStyleDeclaration *)_styleFromFontAttributes:(NSDictionary *)dictionary
4317{
4318    DOMCSSStyleDeclaration *style = [self _emptyStyle];
4319
4320    NSColor *color = [dictionary objectForKey:NSBackgroundColorAttributeName];
4321    [style setBackgroundColor:[self _colorAsString:color]];
4322
4323    NSFont *font = [dictionary objectForKey:NSFontAttributeName];
4324    if (!font) {
4325        [style setFontFamily:@"Helvetica"];
4326        [style setFontSize:@"12px"];
4327        [style setFontWeight:@"normal"];
4328        [style setFontStyle:@"normal"];
4329    } else {
4330        NSFontManager *fm = [NSFontManager sharedFontManager];
4331        // FIXME: Need more sophisticated escaping code if we want to handle family names
4332        // with characters like single quote or backslash in their names.
4333        [style setFontFamily:[NSString stringWithFormat:@"'%@'", [font familyName]]];
4334        [style setFontSize:[NSString stringWithFormat:@"%0.fpx", [font pointSize]]];
4335        // FIXME: Map to the entire range of CSS weight values.
4336        if ([fm weightOfFont:font] >= MIN_BOLD_WEIGHT)
4337            [style setFontWeight:@"bold"];
4338        else
4339            [style setFontWeight:@"normal"];
4340        if ([fm traitsOfFont:font] & NSItalicFontMask)
4341            [style setFontStyle:@"italic"];
4342        else
4343            [style setFontStyle:@"normal"];
4344    }
4345
4346    color = [dictionary objectForKey:NSForegroundColorAttributeName];
4347    [style setColor:color ? [self _colorAsString:color] : (NSString *)@"black"];
4348
4349    NSShadow *shadow = [dictionary objectForKey:NSShadowAttributeName];
4350    [style setTextShadow:[self _shadowAsString:shadow]];
4351
4352    int strikethroughInt = [[dictionary objectForKey:NSStrikethroughStyleAttributeName] intValue];
4353
4354    int superscriptInt = [[dictionary objectForKey:NSSuperscriptAttributeName] intValue];
4355    if (superscriptInt > 0)
4356        [style setVerticalAlign:@"super"];
4357    else if (superscriptInt < 0)
4358        [style setVerticalAlign:@"sub"];
4359    else
4360        [style setVerticalAlign:@"baseline"];
4361    int underlineInt = [[dictionary objectForKey:NSUnderlineStyleAttributeName] intValue];
4362    // FIXME: Underline wins here if we have both (see bug 3790443).
4363    if (strikethroughInt == NSUnderlineStyleNone && underlineInt == NSUnderlineStyleNone)
4364        [style setProperty:@"-webkit-text-decorations-in-effect" value:@"none" priority:@""];
4365    else if (underlineInt == NSUnderlineStyleNone)
4366        [style setProperty:@"-webkit-text-decorations-in-effect" value:@"line-through" priority:@""];
4367    else
4368        [style setProperty:@"-webkit-text-decorations-in-effect" value:@"underline" priority:@""];
4369
4370    return style;
4371}
4372
4373- (void)_applyStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction
4374{
4375    if (Frame* coreFrame = core([self _frame])) {
4376        // FIXME: We shouldn't have to make a copy here. We want callers of this function to work directly with StylePropertySet eventually.
4377        coreFrame->editor().applyStyleToSelection(core(style)->copyProperties().get(), undoAction);
4378    }
4379}
4380
4381- (BOOL)_handleStyleKeyEquivalent:(NSEvent *)event
4382{
4383    WebView *webView = [self _webView];
4384    if (!webView)
4385        return NO;
4386
4387    if (![[webView preferences] respectStandardStyleKeyEquivalents])
4388        return NO;
4389
4390    if (![self _canEdit])
4391        return NO;
4392
4393    if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) != NSCommandKeyMask)
4394        return NO;
4395
4396    NSString *string = [event characters];
4397    if ([string caseInsensitiveCompare:@"b"] == NSOrderedSame) {
4398        [self executeCoreCommandByName:"ToggleBold"];
4399        return YES;
4400    }
4401    if ([string caseInsensitiveCompare:@"i"] == NSOrderedSame) {
4402        [self executeCoreCommandByName:"ToggleItalic"];
4403        return YES;
4404    }
4405
4406    return NO;
4407}
4408
4409- (BOOL)performKeyEquivalent:(NSEvent *)event
4410{
4411    // There's a chance that responding to this event will run a nested event loop, and
4412    // fetching a new event might release the old one. Retaining and then autoreleasing
4413    // the current event prevents that from causing a problem inside WebKit or AppKit code.
4414    [[event retain] autorelease];
4415
4416    BOOL eventWasSentToWebCore = (_private->keyDownEvent == event);
4417    BOOL ret = NO;
4418
4419    [_private->keyDownEvent release];
4420    _private->keyDownEvent = [event retain];
4421
4422    [self retain];
4423
4424    // Pass command-key combos through WebCore if there is a key binding available for
4425    // this event. This lets web pages have a crack at intercepting command-modified keypresses.
4426    // But don't do it if we have already handled the event.
4427    // Pressing Esc results in a fake event being sent - don't pass it to WebCore.
4428    if (!eventWasSentToWebCore && event == [NSApp currentEvent] && self == [[self window] firstResponder])
4429        if (Frame* frame = core([self _frame]))
4430            ret = frame->eventHandler()->keyEvent(event);
4431
4432    if (!ret)
4433        ret = [self _handleStyleKeyEquivalent:event] || [super performKeyEquivalent:event];
4434
4435    [self release];
4436
4437    return ret;
4438}
4439
4440- (void)copyFont:(id)sender
4441{
4442    COMMAND_PROLOGUE
4443
4444    // Put RTF with font attributes on the pasteboard.
4445    // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
4446    NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
4447    [fontPasteboard declareTypes:[NSArray arrayWithObject:NSFontPboardType] owner:nil];
4448    [fontPasteboard setData:[self _selectionStartFontAttributesAsRTF] forType:NSFontPboardType];
4449}
4450
4451- (void)pasteFont:(id)sender
4452{
4453    COMMAND_PROLOGUE
4454
4455    // Read RTF with font attributes from the pasteboard.
4456    // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
4457    [self _applyStyleToSelection:[self _styleFromFontAttributes:[self _fontAttributesFromFontPasteboard]] withUndoAction:EditActionPasteFont];
4458}
4459
4460- (void)pasteAsRichText:(id)sender
4461{
4462    COMMAND_PROLOGUE
4463
4464    // Since rich text always beats plain text when both are on the pasteboard, it's not
4465    // clear how this is different from plain old paste.
4466    [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO];
4467}
4468
4469- (NSFont *)_originalFontA
4470{
4471    return [[NSFontManager sharedFontManager] fontWithFamily:@"Helvetica" traits:0 weight:STANDARD_WEIGHT size:10.0f];
4472}
4473
4474- (NSFont *)_originalFontB
4475{
4476    return [[NSFontManager sharedFontManager] fontWithFamily:@"Times" traits:NSFontItalicTrait weight:STANDARD_BOLD_WEIGHT size:12.0f];
4477}
4478
4479- (void)_addToStyle:(DOMCSSStyleDeclaration *)style fontA:(NSFont *)a fontB:(NSFont *)b
4480{
4481    // Since there's no way to directly ask NSFontManager what style change it's going to do
4482    // we instead pass two "specimen" fonts to it and let it change them. We then deduce what
4483    // style change it was doing by looking at what happened to each of the two fonts.
4484    // So if it was making the text bold, both fonts will be bold after the fact.
4485
4486    if (a == nil || b == nil)
4487        return;
4488
4489    NSFontManager *fm = [NSFontManager sharedFontManager];
4490
4491    NSFont *oa = [self _originalFontA];
4492
4493    NSString *aFamilyName = [a familyName];
4494    NSString *bFamilyName = [b familyName];
4495
4496    int aPointSize = (int)[a pointSize];
4497    int bPointSize = (int)[b pointSize];
4498
4499    int aWeight = [fm weightOfFont:a];
4500    int bWeight = [fm weightOfFont:b];
4501
4502    BOOL aIsItalic = ([fm traitsOfFont:a] & NSItalicFontMask) != 0;
4503    BOOL bIsItalic = ([fm traitsOfFont:b] & NSItalicFontMask) != 0;
4504
4505    BOOL aIsBold = aWeight > MIN_BOLD_WEIGHT;
4506
4507    if ([aFamilyName isEqualToString:bFamilyName]) {
4508        NSString *familyNameForCSS = aFamilyName;
4509
4510        // The family name may not be specific enough to get us the font specified.
4511        // In some cases, the only way to get exactly what we are looking for is to use
4512        // the Postscript name.
4513
4514        // Find the font the same way the rendering code would later if it encountered this CSS.
4515        NSFontTraitMask traits = aIsItalic ? NSFontItalicTrait : 0;
4516        int weight = aIsBold ? STANDARD_BOLD_WEIGHT : STANDARD_WEIGHT;
4517        NSFont *foundFont = [WebFontCache fontWithFamily:aFamilyName traits:traits weight:weight size:aPointSize];
4518
4519        // If we don't find a font with the same Postscript name, then we'll have to use the
4520        // Postscript name to make the CSS specific enough.
4521        if (![[foundFont fontName] isEqualToString:[a fontName]])
4522            familyNameForCSS = [a fontName];
4523
4524        // FIXME: Need more sophisticated escaping code if we want to handle family names
4525        // with characters like single quote or backslash in their names.
4526        [style setFontFamily:[NSString stringWithFormat:@"'%@'", familyNameForCSS]];
4527    }
4528
4529    int soa = (int)[oa pointSize];
4530    if (aPointSize == bPointSize)
4531        [style setFontSize:[NSString stringWithFormat:@"%dpx", aPointSize]];
4532    else if (aPointSize < soa)
4533        [style _setFontSizeDelta:@"-1px"];
4534    else if (aPointSize > soa)
4535        [style _setFontSizeDelta:@"1px"];
4536
4537    // FIXME: Map to the entire range of CSS weight values.
4538    if (aWeight == bWeight)
4539        [style setFontWeight:aIsBold ? @"bold" : @"normal"];
4540
4541    if (aIsItalic == bIsItalic)
4542        [style setFontStyle:aIsItalic ? @"italic" :  @"normal"];
4543}
4544
4545- (DOMCSSStyleDeclaration *)_styleFromFontManagerOperation
4546{
4547    DOMCSSStyleDeclaration *style = [self _emptyStyle];
4548
4549    NSFontManager *fm = [NSFontManager sharedFontManager];
4550
4551    NSFont *oa = [self _originalFontA];
4552    NSFont *ob = [self _originalFontB];
4553    [self _addToStyle:style fontA:[fm convertFont:oa] fontB:[fm convertFont:ob]];
4554
4555    return style;
4556}
4557
4558- (void)changeFont:(id)sender
4559{
4560    COMMAND_PROLOGUE
4561
4562    [self _applyStyleToSelection:[self _styleFromFontManagerOperation] withUndoAction:EditActionSetFont];
4563}
4564
4565- (DOMCSSStyleDeclaration *)_styleForAttributeChange:(id)sender
4566{
4567    DOMCSSStyleDeclaration *style = [self _emptyStyle];
4568
4569    NSShadow *shadow = [[NSShadow alloc] init];
4570    [shadow setShadowOffset:NSMakeSize(1, 1)];
4571
4572    NSDictionary *oa = [NSDictionary dictionaryWithObjectsAndKeys:
4573        [self _originalFontA], NSFontAttributeName,
4574        nil];
4575    NSDictionary *ob = [NSDictionary dictionaryWithObjectsAndKeys:
4576        [NSColor blackColor], NSBackgroundColorAttributeName,
4577        [self _originalFontB], NSFontAttributeName,
4578        [NSColor whiteColor], NSForegroundColorAttributeName,
4579        shadow, NSShadowAttributeName,
4580        [NSNumber numberWithInt:NSUnderlineStyleSingle], NSStrikethroughStyleAttributeName,
4581        [NSNumber numberWithInt:1], NSSuperscriptAttributeName,
4582        [NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName,
4583        nil];
4584
4585    [shadow release];
4586
4587    NSDictionary *a = [sender convertAttributes:oa];
4588    NSDictionary *b = [sender convertAttributes:ob];
4589
4590    NSColor *ca = [a objectForKey:NSBackgroundColorAttributeName];
4591    NSColor *cb = [b objectForKey:NSBackgroundColorAttributeName];
4592    if (ca == cb) {
4593        [style setBackgroundColor:[self _colorAsString:ca]];
4594    }
4595
4596    [self _addToStyle:style fontA:[a objectForKey:NSFontAttributeName] fontB:[b objectForKey:NSFontAttributeName]];
4597
4598    ca = [a objectForKey:NSForegroundColorAttributeName];
4599    cb = [b objectForKey:NSForegroundColorAttributeName];
4600    if (ca == cb) {
4601        if (!ca)
4602            ca = [NSColor blackColor];
4603        [style setColor:[self _colorAsString:ca]];
4604    }
4605
4606    NSShadow *sha = [a objectForKey:NSShadowAttributeName];
4607    if (sha)
4608        [style setTextShadow:[self _shadowAsString:sha]];
4609    else if ([b objectForKey:NSShadowAttributeName] == nil)
4610        [style setTextShadow:@"none"];
4611
4612    int sa = [[a objectForKey:NSStrikethroughStyleAttributeName] intValue];
4613    int sb = [[b objectForKey:NSStrikethroughStyleAttributeName] intValue];
4614    if (sa == sb) {
4615        if (sa == NSUnderlineStyleNone)
4616            [style setProperty:@"-webkit-text-decorations-in-effect" value:@"none" priority:@""];
4617            // we really mean "no line-through" rather than "none"
4618        else
4619            [style setProperty:@"-webkit-text-decorations-in-effect" value:@"line-through" priority:@""];
4620            // we really mean "add line-through" rather than "line-through"
4621    }
4622
4623    sa = [[a objectForKey:NSSuperscriptAttributeName] intValue];
4624    sb = [[b objectForKey:NSSuperscriptAttributeName] intValue];
4625    if (sa == sb) {
4626        if (sa > 0)
4627            [style setVerticalAlign:@"super"];
4628        else if (sa < 0)
4629            [style setVerticalAlign:@"sub"];
4630        else
4631            [style setVerticalAlign:@"baseline"];
4632    }
4633
4634    int ua = [[a objectForKey:NSUnderlineStyleAttributeName] intValue];
4635    int ub = [[b objectForKey:NSUnderlineStyleAttributeName] intValue];
4636    if (ua == ub) {
4637        if (ua == NSUnderlineStyleNone)
4638            [style setProperty:@"-webkit-text-decorations-in-effect" value:@"none" priority:@""];
4639            // we really mean "no underline" rather than "none"
4640        else
4641            [style setProperty:@"-webkit-text-decorations-in-effect" value:@"underline" priority:@""];
4642            // we really mean "add underline" rather than "underline"
4643    }
4644
4645    return style;
4646}
4647
4648- (void)changeAttributes:(id)sender
4649{
4650    COMMAND_PROLOGUE
4651
4652    [self _applyStyleToSelection:[self _styleForAttributeChange:sender] withUndoAction:EditActionChangeAttributes];
4653}
4654
4655- (DOMCSSStyleDeclaration *)_styleFromColorPanelWithSelector:(SEL)selector
4656{
4657    DOMCSSStyleDeclaration *style = [self _emptyStyle];
4658
4659    ASSERT([style respondsToSelector:selector]);
4660    [style performSelector:selector withObject:[self _colorAsString:[[NSColorPanel sharedColorPanel] color]]];
4661
4662    return style;
4663}
4664
4665- (EditAction)_undoActionFromColorPanelWithSelector:(SEL)selector
4666{
4667    if (selector == @selector(setBackgroundColor:))
4668        return EditActionSetBackgroundColor;
4669    return EditActionSetColor;
4670}
4671
4672- (void)_changeCSSColorUsingSelector:(SEL)selector inRange:(DOMRange *)range
4673{
4674    DOMCSSStyleDeclaration *style = [self _styleFromColorPanelWithSelector:selector];
4675    WebView *webView = [self _webView];
4676    if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:range]) {
4677        if (Frame* coreFrame = core([self _frame])) {
4678            // FIXME: We shouldn't have to make a copy here.
4679            coreFrame->editor().applyStyle(core(style)->copyProperties().get(), [self _undoActionFromColorPanelWithSelector:selector]);
4680        }
4681    }
4682
4683}
4684
4685- (void)changeDocumentBackgroundColor:(id)sender
4686{
4687    COMMAND_PROLOGUE
4688
4689    // Mimicking NSTextView, this method sets the background color for the
4690    // entire document. There is no NSTextView API for setting the background
4691    // color on the selected range only. Note that this method is currently
4692    // never called from the UI (see comment in changeColor:).
4693    // FIXME: this actually has no effect when called, probably due to 3654850. _documentRange seems
4694    // to do the right thing because it works in startSpeaking:, and I know setBackgroundColor: does the
4695    // right thing because I tested it with [self _selectedRange].
4696    // FIXME: This won't actually apply the style to the entire range here, because it ends up calling
4697    // [frame _applyStyle:], which operates on the current selection. To make this work right, we'll
4698    // need to save off the selection, temporarily set it to the entire range, make the change, then
4699    // restore the old selection.
4700    [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _documentRange]];
4701}
4702
4703- (void)changeColor:(id)sender
4704{
4705    COMMAND_PROLOGUE
4706
4707    // FIXME: in NSTextView, this method calls changeDocumentBackgroundColor: when a
4708    // private call has earlier been made by [NSFontFontEffectsBox changeColor:], see 3674493.
4709    // AppKit will have to be revised to allow this to work with anything that isn't an
4710    // NSTextView. However, this might not be required for Tiger, since the background-color
4711    // changing box in the font panel doesn't work in Mail (3674481), though it does in TextEdit.
4712    [self _applyStyleToSelection:[self _styleFromColorPanelWithSelector:@selector(setColor:)]
4713                  withUndoAction:EditActionSetColor];
4714}
4715
4716- (void)_changeWordCaseWithSelector:(SEL)selector
4717{
4718    if (![self _canEdit])
4719        return;
4720
4721    WebFrame *frame = [self _frame];
4722    [self selectWord:nil];
4723    NSString *word = [[frame _selectedString] performSelector:selector];
4724    // FIXME: Does this need a different action context other than "typed"?
4725    if ([self _shouldReplaceSelectionWithText:word givenAction:WebViewInsertActionTyped])
4726        [frame _replaceSelectionWithText:word selectReplacement:NO smartReplace:NO];
4727}
4728
4729- (void)uppercaseWord:(id)sender
4730{
4731    COMMAND_PROLOGUE
4732
4733    [self _changeWordCaseWithSelector:@selector(uppercaseString)];
4734}
4735
4736- (void)lowercaseWord:(id)sender
4737{
4738    COMMAND_PROLOGUE
4739
4740    [self _changeWordCaseWithSelector:@selector(lowercaseString)];
4741}
4742
4743- (void)capitalizeWord:(id)sender
4744{
4745    COMMAND_PROLOGUE
4746
4747    [self _changeWordCaseWithSelector:@selector(capitalizedString)];
4748}
4749
4750- (void)complete:(id)sender
4751{
4752    COMMAND_PROLOGUE
4753
4754    if (![self _canEdit])
4755        return;
4756    if (!_private->completionController)
4757        _private->completionController = [[WebTextCompletionController alloc] initWithWebView:[self _webView] HTMLView:self];
4758    [_private->completionController doCompletion];
4759}
4760
4761- (void)checkSpelling:(id)sender
4762{
4763    COMMAND_PROLOGUE
4764
4765    if (Frame* coreFrame = core([self _frame]))
4766        coreFrame->editor().advanceToNextMisspelling();
4767}
4768
4769- (void)showGuessPanel:(id)sender
4770{
4771    COMMAND_PROLOGUE
4772
4773    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
4774    if (!checker) {
4775        LOG_ERROR("No NSSpellChecker");
4776        return;
4777    }
4778
4779    NSPanel *spellingPanel = [checker spellingPanel];
4780    if ([spellingPanel isVisible]) {
4781        [spellingPanel orderOut:sender];
4782        return;
4783    }
4784
4785    if (Frame* coreFrame = core([self _frame]))
4786        coreFrame->editor().advanceToNextMisspelling(true);
4787    [spellingPanel orderFront:sender];
4788}
4789
4790- (void)_changeSpellingToWord:(NSString *)newWord
4791{
4792    if (![self _canEdit])
4793        return;
4794
4795    // Don't correct to empty string.  (AppKit checked this, we might as well too.)
4796    if (![NSSpellChecker sharedSpellChecker]) {
4797        LOG_ERROR("No NSSpellChecker");
4798        return;
4799    }
4800
4801    if ([newWord isEqualToString:@""])
4802        return;
4803
4804    if ([self _shouldReplaceSelectionWithText:newWord givenAction:WebViewInsertActionPasted])
4805        [[self _frame] _replaceSelectionWithText:newWord selectReplacement:YES smartReplace:NO];
4806}
4807
4808- (void)changeSpelling:(id)sender
4809{
4810    COMMAND_PROLOGUE
4811
4812    [self _changeSpellingToWord:[[sender selectedCell] stringValue]];
4813}
4814
4815- (void)performFindPanelAction:(id)sender
4816{
4817    COMMAND_PROLOGUE
4818
4819    // Implementing this will probably require copying all of NSFindPanel.h and .m.
4820    // We need *almost* the same thing as AppKit, but not quite.
4821    LOG_ERROR("unimplemented");
4822}
4823
4824- (void)startSpeaking:(id)sender
4825{
4826    COMMAND_PROLOGUE
4827
4828    WebFrame *frame = [self _frame];
4829    DOMRange *range = [self _selectedRange];
4830    if (!range || [range collapsed])
4831        range = [self _documentRange];
4832    [NSApp speakString:[frame _stringForRange:range]];
4833}
4834
4835- (void)stopSpeaking:(id)sender
4836{
4837    COMMAND_PROLOGUE
4838
4839    [NSApp stopSpeaking:sender];
4840}
4841
4842- (void)toggleBaseWritingDirection:(id)sender
4843{
4844    COMMAND_PROLOGUE
4845
4846    if (![self _canEdit])
4847        return;
4848
4849    Frame* coreFrame = core([self _frame]);
4850    if (!coreFrame)
4851        return;
4852
4853    WritingDirection direction = RightToLeftWritingDirection;
4854    switch (coreFrame->editor().baseWritingDirectionForSelectionStart()) {
4855        case LeftToRightWritingDirection:
4856            break;
4857        case RightToLeftWritingDirection:
4858            direction = LeftToRightWritingDirection;
4859            break;
4860        // The writingDirectionForSelectionStart method will never return "natural". It
4861        // will always return a concrete direction. So, keep the compiler happy, and assert not reached.
4862        case NaturalWritingDirection:
4863            ASSERT_NOT_REACHED();
4864            break;
4865    }
4866
4867    if (Frame* coreFrame = core([self _frame]))
4868        coreFrame->editor().setBaseWritingDirection(direction);
4869}
4870
4871- (void)changeBaseWritingDirection:(id)sender
4872{
4873    COMMAND_PROLOGUE
4874
4875    if (![self _canEdit])
4876        return;
4877
4878    NSWritingDirection writingDirection = static_cast<NSWritingDirection>([sender tag]);
4879
4880    // We disable the menu item that performs this action because we can't implement
4881    // NSWritingDirectionNatural's behavior using CSS.
4882    ASSERT(writingDirection != NSWritingDirectionNatural);
4883
4884    if (Frame* coreFrame = core([self _frame]))
4885        coreFrame->editor().setBaseWritingDirection(writingDirection == NSWritingDirectionLeftToRight ? LeftToRightWritingDirection : RightToLeftWritingDirection);
4886}
4887
4888static BOOL writingDirectionKeyBindingsEnabled()
4889{
4890    return YES;
4891}
4892
4893- (void)_changeBaseWritingDirectionTo:(NSWritingDirection)direction
4894{
4895    if (![self _canEdit])
4896        return;
4897
4898    static BOOL bindingsEnabled = writingDirectionKeyBindingsEnabled();
4899
4900    if (!bindingsEnabled) {
4901        NSBeep();
4902        return;
4903    }
4904
4905    if (Frame* coreFrame = core([self _frame]))
4906        coreFrame->editor().setBaseWritingDirection(direction == NSWritingDirectionLeftToRight ? LeftToRightWritingDirection : RightToLeftWritingDirection);
4907}
4908
4909- (void)makeBaseWritingDirectionLeftToRight:(id)sender
4910{
4911    COMMAND_PROLOGUE
4912
4913    [self _changeBaseWritingDirectionTo:NSWritingDirectionLeftToRight];
4914}
4915
4916- (void)makeBaseWritingDirectionRightToLeft:(id)sender
4917{
4918    COMMAND_PROLOGUE
4919
4920    [self _changeBaseWritingDirectionTo:NSWritingDirectionRightToLeft];
4921}
4922
4923- (void)makeBaseWritingDirectionNatural:(id)sender
4924{
4925    LOG_ERROR("Sent from %@.", sender);
4926}
4927
4928#if 0
4929
4930// CSS does not have a way to specify an outline font, which may make this difficult to implement.
4931// Maybe a special case of text-shadow?
4932- (void)outline:(id)sender;
4933
4934// This is part of table support, which may be in NSTextView for Tiger.
4935// It's probably simple to do the equivalent thing for WebKit.
4936- (void)insertTable:(id)sender;
4937
4938// This could be important.
4939- (void)toggleTraditionalCharacterShape:(id)sender;
4940
4941// I'm not sure what the equivalents of these in the web world are.
4942- (void)insertLineSeparator:(id)sender;
4943- (void)insertPageBreak:(id)sender;
4944
4945// These methods are not implemented in NSTextView yet at the time of this writing.
4946- (void)changeCaseOfLetter:(id)sender;
4947- (void)transposeWords:(id)sender;
4948
4949#endif
4950
4951
4952// Override this so that AppKit will send us arrow keys as key down events so we can
4953// support them via the key bindings mechanism.
4954- (BOOL)_wantsKeyDownForEvent:(NSEvent *)event
4955{
4956    bool haveWebCoreFrame = core([self _frame]);
4957
4958    // If we have a frame, our keyDown method will handle key bindings after sending
4959    // the event through the DOM, so ask AppKit not to do its early special key binding
4960    // mapping. If we don't have a frame, just let things work the normal way without
4961    // a keyDown.
4962    return haveWebCoreFrame;
4963}
4964
4965#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
4966- (BOOL)_automaticFocusRingDisabled
4967{
4968    // The default state for _automaticFocusRingDisabled is NO, which prevents focus rings
4969    // from being painted for search fields. Calling NSSetFocusRingStyle has the side effect
4970    // of changing this to YES, so just return YES all the time. <rdar://problem/13780122>,
4971    return YES;
4972}
4973#endif
4974
4975- (void)_updateControlTints
4976{
4977    Frame* frame = core([self _frame]);
4978    if (!frame)
4979        return;
4980    FrameView* view = frame->view();
4981    if (!view)
4982        return;
4983    view->updateControlTints();
4984}
4985
4986// Despite its name, this is called at different times than windowDidBecomeKey is.
4987// It takes into account all the other factors that determine when NSCell draws
4988// with different tints, so it's the right call to use for control tints. We'd prefer
4989// to do this with API. <rdar://problem/5136760>
4990- (void)_windowChangedKeyState
4991{
4992    if (pthread_main_np())
4993        [self _updateControlTints];
4994    else
4995        [self performSelectorOnMainThread:@selector(_updateControlTints) withObject:nil waitUntilDone:NO];
4996
4997    [super _windowChangedKeyState];
4998}
4999
5000- (void)otherMouseDown:(NSEvent *)event
5001{
5002    if ([event buttonNumber] == 2)
5003        [self mouseDown:event];
5004    else
5005        [super otherMouseDown:event];
5006}
5007
5008- (void)otherMouseDragged:(NSEvent *)event
5009{
5010    if ([event buttonNumber] == 2)
5011        [self mouseDragged:event];
5012    else
5013        [super otherMouseDragged:event];
5014}
5015
5016- (void)otherMouseUp:(NSEvent *)event
5017{
5018    if ([event buttonNumber] == 2)
5019        [self mouseUp:event];
5020    else
5021        [super otherMouseUp:event];
5022}
5023
5024@end
5025
5026@implementation WebHTMLView (WebInternal)
5027
5028- (void)_selectionChanged
5029{
5030    [self _updateSelectionForInputManager];
5031    [self _updateFontPanel];
5032}
5033
5034- (void)_updateFontPanel
5035{
5036    // FIXME: NSTextView bails out if becoming or resigning first responder, for which it has ivar flags. Not
5037    // sure if we need to do something similar.
5038
5039    if (![self _canEdit])
5040        return;
5041
5042    NSWindow *window = [self window];
5043    // FIXME: is this first-responder check correct? What happens if a subframe is editable and is first responder?
5044    if (![window isKeyWindow] || [window firstResponder] != self)
5045        return;
5046
5047    bool multipleFonts = false;
5048    NSFont *font = nil;
5049    if (Frame* coreFrame = core([self _frame])) {
5050        if (const SimpleFontData* fd = coreFrame->editor().fontForSelection(multipleFonts))
5051            font = fd->getNSFont();
5052    }
5053
5054    // FIXME: for now, return a bogus font that distinguishes the empty selection from the non-empty
5055    // selection. We should be able to remove this once the rest of this code works properly.
5056    if (font == nil)
5057        font = [self _hasSelection] ? [NSFont menuFontOfSize:23] : [NSFont toolTipsFontOfSize:17];
5058    ASSERT(font != nil);
5059
5060    [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:multipleFonts];
5061}
5062
5063- (BOOL)_canSmartCopyOrDelete
5064{
5065    if (![[self _webView] smartInsertDeleteEnabled])
5066        return NO;
5067    Frame* coreFrame = core([self _frame]);
5068    return coreFrame && coreFrame->selection()->granularity() == WordGranularity;
5069}
5070
5071- (NSEvent *)_mouseDownEvent
5072{
5073    return _private->mouseDownEvent;
5074}
5075
5076- (id<WebHTMLHighlighter>)_highlighterForType:(NSString*)type
5077{
5078    return [_private->highlighters objectForKey:type];
5079}
5080
5081- (WebFrame *)_frame
5082{
5083    return [_private->dataSource webFrame];
5084}
5085
5086- (void)closeIfNotCurrentView
5087{
5088    if ([[[self _frame] frameView] documentView] != self)
5089        [self close];
5090}
5091
5092- (DOMDocumentFragment*)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard
5093{
5094    return [self _documentFragmentFromPasteboard:pasteboard inContext:nil allowPlainText:NO];
5095}
5096
5097
5098- (BOOL)isGrammarCheckingEnabled
5099{
5100    // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because
5101    // the AppKit code checks the first responder.
5102    return [[self _webView] isGrammarCheckingEnabled];
5103}
5104
5105- (void)setGrammarCheckingEnabled:(BOOL)flag
5106{
5107    // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because
5108    // the AppKit code checks the first responder.
5109    [[self _webView] setGrammarCheckingEnabled:flag];
5110}
5111
5112- (void)toggleGrammarChecking:(id)sender
5113{
5114    // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because
5115    // the AppKit code checks the first responder.
5116    [[self _webView] toggleGrammarChecking:sender];
5117}
5118
5119- (void)orderFrontSubstitutionsPanel:(id)sender
5120{
5121    COMMAND_PROLOGUE
5122
5123    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
5124    if (!checker) {
5125        LOG_ERROR("No NSSpellChecker");
5126        return;
5127    }
5128
5129    NSPanel *substitutionsPanel = [checker substitutionsPanel];
5130    if ([substitutionsPanel isVisible]) {
5131        [substitutionsPanel orderOut:sender];
5132        return;
5133    }
5134    [substitutionsPanel orderFront:sender];
5135}
5136
5137// FIXME 4799134: WebView is the bottleneck for this logic, but we must implement these methods here because
5138// the AppKit code checks the first responder.
5139
5140- (BOOL)smartInsertDeleteEnabled
5141{
5142    return [[self _webView] smartInsertDeleteEnabled];
5143}
5144
5145- (void)setSmartInsertDeleteEnabled:(BOOL)flag
5146{
5147    [[self _webView] setSmartInsertDeleteEnabled:flag];
5148}
5149
5150- (void)toggleSmartInsertDelete:(id)sender
5151{
5152    [[self _webView] toggleSmartInsertDelete:sender];
5153}
5154
5155- (BOOL)isAutomaticQuoteSubstitutionEnabled
5156{
5157    return [[self _webView] isAutomaticQuoteSubstitutionEnabled];
5158}
5159
5160- (void)setAutomaticQuoteSubstitutionEnabled:(BOOL)flag
5161{
5162    [[self _webView] setAutomaticQuoteSubstitutionEnabled:flag];
5163}
5164
5165- (void)toggleAutomaticQuoteSubstitution:(id)sender
5166{
5167    [[self _webView] toggleAutomaticQuoteSubstitution:sender];
5168}
5169
5170- (BOOL)isAutomaticLinkDetectionEnabled
5171{
5172    return [[self _webView] isAutomaticLinkDetectionEnabled];
5173}
5174
5175- (void)setAutomaticLinkDetectionEnabled:(BOOL)flag
5176{
5177    [[self _webView] setAutomaticLinkDetectionEnabled:flag];
5178}
5179
5180- (void)toggleAutomaticLinkDetection:(id)sender
5181{
5182    [[self _webView] toggleAutomaticLinkDetection:sender];
5183}
5184
5185- (BOOL)isAutomaticDashSubstitutionEnabled
5186{
5187    return [[self _webView] isAutomaticDashSubstitutionEnabled];
5188}
5189
5190- (void)setAutomaticDashSubstitutionEnabled:(BOOL)flag
5191{
5192    [[self _webView] setAutomaticDashSubstitutionEnabled:flag];
5193}
5194
5195- (void)toggleAutomaticDashSubstitution:(id)sender
5196{
5197    [[self _webView] toggleAutomaticDashSubstitution:sender];
5198}
5199
5200- (BOOL)isAutomaticTextReplacementEnabled
5201{
5202    return [[self _webView] isAutomaticTextReplacementEnabled];
5203}
5204
5205- (void)setAutomaticTextReplacementEnabled:(BOOL)flag
5206{
5207    [[self _webView] setAutomaticTextReplacementEnabled:flag];
5208}
5209
5210- (void)toggleAutomaticTextReplacement:(id)sender
5211{
5212    [[self _webView] toggleAutomaticTextReplacement:sender];
5213}
5214
5215- (BOOL)isAutomaticSpellingCorrectionEnabled
5216{
5217    return [[self _webView] isAutomaticSpellingCorrectionEnabled];
5218}
5219
5220- (void)setAutomaticSpellingCorrectionEnabled:(BOOL)flag
5221{
5222    [[self _webView] setAutomaticSpellingCorrectionEnabled:flag];
5223}
5224
5225- (void)toggleAutomaticSpellingCorrection:(id)sender
5226{
5227    [[self _webView] toggleAutomaticSpellingCorrection:sender];
5228}
5229
5230- (void)_lookUpInDictionaryFromMenu:(id)sender
5231{
5232    // Dictionary API will accept a whitespace-only string and display UI as if it were real text,
5233    // so bail out early to avoid that.
5234    if ([[[self selectedString] _webkit_stringByTrimmingWhitespace] length] == 0)
5235        return;
5236
5237    NSAttributedString *attrString = [self selectedAttributedString];
5238
5239    Frame* coreFrame = core([self _frame]);
5240    if (!coreFrame)
5241        return;
5242
5243    NSRect rect = coreFrame->selection()->bounds();
5244
5245    NSDictionary *attributes = [attrString fontAttributesInRange:NSMakeRange(0,1)];
5246    NSFont *font = [attributes objectForKey:NSFontAttributeName];
5247    if (font)
5248        rect.origin.y += [font ascender];
5249
5250    [self showDefinitionForAttributedString:attrString atPoint:rect.origin];
5251}
5252
5253- (void)_executeSavedKeypressCommands
5254{
5255    WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
5256    if (!parameters || parameters->event->keypressCommands().isEmpty())
5257        return;
5258
5259    // We could be called again if the execution of one command triggers a call to selectedRange.
5260    // In this case, the state is up to date, and we don't need to execute any more saved commands to return a result
5261    if (parameters->executingSavedKeypressCommands)
5262        return;
5263
5264    // Avoid an infinite loop that would occur if executing a command appended it to event->keypressCommands() again.
5265    bool wasSavingCommands = parameters->shouldSaveCommands;
5266    parameters->shouldSaveCommands = false;
5267    parameters->executingSavedKeypressCommands = true;
5268
5269    const Vector<KeypressCommand>& commands = parameters->event->keypressCommands();
5270
5271    for (size_t i = 0; i < commands.size(); ++i) {
5272        if (commands[i].commandName == "insertText:")
5273            [self insertText:commands[i].text];
5274        else if (commands[i].commandName == "noop:")
5275            ; // Do nothing. This case can be removed once <rdar://problem/9025012> is fixed.
5276        else
5277            [self doCommandBySelector:NSSelectorFromString(commands[i].commandName)];
5278    }
5279    parameters->event->keypressCommands().clear();
5280    parameters->shouldSaveCommands = wasSavingCommands;
5281    parameters->executingSavedKeypressCommands = false;
5282}
5283
5284- (BOOL)_interpretKeyEvent:(KeyboardEvent*)event savingCommands:(BOOL)savingCommands
5285{
5286    ASSERT(core([self _frame]) == event->target()->toNode()->document()->frame());
5287    ASSERT(!savingCommands || event->keypressCommands().isEmpty()); // Save commands once for each event.
5288
5289    WebHTMLViewInterpretKeyEventsParameters parameters;
5290    parameters.eventInterpretationHadSideEffects = false;
5291    parameters.shouldSaveCommands = savingCommands;
5292    parameters.executingSavedKeypressCommands = false;
5293    // If we're intercepting the initial IM call we assume that the IM has consumed the event,
5294    // and only change this assumption if one of the NSTextInput/Responder callbacks is used.
5295    // We assume the IM will *not* consume hotkey sequences
5296    parameters.consumedByIM = savingCommands && !event->metaKey();
5297
5298    const PlatformKeyboardEvent* platformEvent = event->keyEvent();
5299    if (!platformEvent)
5300        return NO;
5301
5302    NSEvent *macEvent = platformEvent->macEvent();
5303    if ([macEvent type] == NSKeyDown && [_private->completionController filterKeyDown:macEvent])
5304        return YES;
5305
5306    if ([macEvent type] == NSFlagsChanged)
5307        return NO;
5308
5309    parameters.event = event;
5310    _private->interpretKeyEventsParameters = &parameters;
5311    const Vector<KeypressCommand>& commands = event->keypressCommands();
5312
5313    if (savingCommands) {
5314        // AppKit will respond with a series of NSTextInput protocol method calls. There are three groups that we heuristically differentiate:
5315        // 1. Key Bindings. Only doCommandBySelector: and insertText: calls will be made, which we save in the event for execution
5316        // after DOM dispatch. This is safe, because neither returns a result, so there is no branching on AppKit side.
5317        // 2. Plain text input. Here as well, we need to dispatch DOM events prior to inserting text, so we save the insertText: command.
5318        // 3. Input method processing. An IM can make any NSTextInput calls, and can base its decisions on results it gets, so we must
5319        // execute the calls immediately. DOM events like keydown are tweaked to have keyCode of 229, and canceling them has no effect.
5320        // Unfortunately, there is no real difference between plain text input and IM processing - for example, AppKit queries hasMarkedText
5321        // when typing with U.S. keyboard, and inserts marked text for dead keys.
5322        [self interpretKeyEvents:[NSArray arrayWithObject:macEvent]];
5323    } else {
5324        // Are there commands that could just cause text insertion if executed via Editor?
5325        // WebKit doesn't have enough information about mode to decide how they should be treated, so we leave it upon WebCore
5326        // to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated
5327        // (e.g. Tab that inserts a Tab character, or Enter).
5328        bool haveTextInsertionCommands = false;
5329        for (size_t i = 0; i < commands.size(); ++i) {
5330            if ([self coreCommandBySelector:NSSelectorFromString(commands[i].commandName)].isTextInsertion())
5331                haveTextInsertionCommands = true;
5332        }
5333        // If there are no text insertion commands, default keydown handler is the right time to execute the commands.
5334        // Keypress (Char event) handler is the latest opportunity to execute.
5335        if (!haveTextInsertionCommands || platformEvent->type() == PlatformEvent::Char)
5336            [self _executeSavedKeypressCommands];
5337    }
5338    _private->interpretKeyEventsParameters = 0;
5339
5340    // An input method may make several actions per keypress. For example, pressing Return with Korean IM both confirms it and sends a newline.
5341    // IM-like actions are handled immediately (so parameters.eventInterpretationHadSideEffects is true), but there are saved commands that
5342    // should be handled like normal text input after DOM event dispatch.
5343    if (!event->keypressCommands().isEmpty())
5344        return NO;
5345
5346    // An input method may consume an event and not tell us (e.g. when displaying a candidate window),
5347    // in which case we should not bubble the event up the DOM.
5348    if (parameters.consumedByIM)
5349        return YES;
5350
5351    // If we have already executed all commands, don't do it again.
5352    return parameters.eventInterpretationHadSideEffects;
5353}
5354
5355- (WebCore::CachedImage*)promisedDragTIFFDataSource
5356{
5357    return _private->promisedDragTIFFDataSource;
5358}
5359
5360- (void)setPromisedDragTIFFDataSource:(WebCore::CachedImage*)source
5361{
5362    if (source)
5363        source->addClient(promisedDataClient());
5364
5365    if (_private->promisedDragTIFFDataSource)
5366        _private->promisedDragTIFFDataSource->removeClient(promisedDataClient());
5367    _private->promisedDragTIFFDataSource = source;
5368}
5369
5370#undef COMMAND_PROLOGUE
5371
5372- (void)_layoutIfNeeded
5373{
5374    ASSERT(!_private->subviewsSetAside);
5375
5376    if ([self _needsLayout])
5377        [self layout];
5378}
5379
5380- (void)_web_updateLayoutAndStyleIfNeededRecursive
5381{
5382    WebFrame *webFrame = [self _frame];
5383    Frame* coreFrame = core(webFrame);
5384    if (coreFrame && coreFrame->view())
5385        coreFrame->view()->updateLayoutAndStyleIfNeededRecursive();
5386}
5387
5388- (void) _destroyAllWebPlugins
5389{
5390    [[self _pluginController] destroyAllPlugins];
5391}
5392
5393- (BOOL)_needsLayout
5394{
5395    return [[self _frame] _needsLayout];
5396}
5397
5398#if USE(ACCELERATED_COMPOSITING)
5399- (void)attachRootLayer:(CALayer*)layer
5400{
5401    if (!_private->layerHostingView) {
5402        NSView* hostingView = [[WebLayerHostingFlippedView alloc] initWithFrame:[self bounds]];
5403        [hostingView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
5404        [self addSubview:hostingView];
5405        [hostingView release];
5406        // hostingView is owned by being a subview of self
5407        _private->layerHostingView = hostingView;
5408    }
5409
5410    // Make a container layer, which will get sized/positioned by AppKit and CA.
5411    CALayer* viewLayer = [WebRootLayer layer];
5412
5413    if ([self layer]) {
5414        // If we are in a layer-backed view, we need to manually initialize the geometry for our layer.
5415        [viewLayer setBounds:NSRectToCGRect([_private->layerHostingView bounds])];
5416        [viewLayer setAnchorPoint:CGPointMake(0, [self isFlipped] ? 1 : 0)];
5417        CGPoint layerPosition = NSPointToCGPoint([self convertPointToBase:[_private->layerHostingView frame].origin]);
5418        [viewLayer setPosition:layerPosition];
5419    }
5420
5421    [_private->layerHostingView setLayer:viewLayer];
5422    [_private->layerHostingView setWantsLayer:YES];
5423
5424    // Parent our root layer in the container layer
5425    [viewLayer addSublayer:layer];
5426
5427    if ([[self _webView] _postsAcceleratedCompositingNotifications])
5428        [[NSNotificationCenter defaultCenter] postNotificationName:_WebViewDidStartAcceleratedCompositingNotification object:[self _webView] userInfo:nil];
5429
5430#if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1070
5431    // Do geometry flipping here, which flips all the compositing layers so they are top-down.
5432    [viewLayer setGeometryFlipped:YES];
5433#else
5434    if (WKExecutableWasLinkedOnOrBeforeLion())
5435        [viewLayer setGeometryFlipped:YES];
5436#endif
5437}
5438
5439- (void)detachRootLayer
5440{
5441    if (_private->layerHostingView) {
5442        [_private->layerHostingView setLayer:nil];
5443        [_private->layerHostingView setWantsLayer:NO];
5444        [_private->layerHostingView removeFromSuperview];
5445        _private->layerHostingView = nil;
5446    }
5447}
5448
5449- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
5450{
5451    if (_private) {
5452        ASSERT(!_private->drawingIntoLayer);
5453        _private->drawingIntoLayer = YES;
5454    }
5455
5456    [super drawLayer:layer inContext:ctx];
5457
5458    if (_private)
5459        _private->drawingIntoLayer = NO;
5460}
5461
5462- (BOOL)_web_isDrawingIntoLayer
5463{
5464    return _private->drawingIntoLayer;
5465}
5466
5467#endif // USE(ACCELERATED_COMPOSITING)
5468
5469@end
5470
5471@implementation WebHTMLView (WebNSTextInputSupport)
5472
5473- (NSArray *)validAttributesForMarkedText
5474{
5475    static NSArray *validAttributes;
5476    if (!validAttributes) {
5477        validAttributes = [[NSArray alloc] initWithObjects:
5478            NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName,
5479            NSMarkedClauseSegmentAttributeName, NSTextInputReplacementRangeAttributeName,
5480#if USE(DICTATION_ALTERNATIVES)
5481                           NSTextAlternativesAttributeName,
5482#endif
5483                           nil];
5484        // NSText also supports the following attributes, but it's
5485        // hard to tell which are really required for text input to
5486        // work well; I have not seen any input method make use of them yet.
5487        //     NSFontAttributeName, NSForegroundColorAttributeName,
5488        //     NSBackgroundColorAttributeName, NSLanguageAttributeName.
5489        CFRetain(validAttributes);
5490    }
5491    LOG(TextInput, "validAttributesForMarkedText -> (...)");
5492    return validAttributes;
5493}
5494
5495- (NSTextInputContext *)inputContext
5496{
5497    return _private->exposeInputContext ? [super inputContext] : nil;
5498}
5499
5500- (NSAttributedString *)textStorage
5501{
5502    if (!_private->exposeInputContext) {
5503        LOG(TextInput, "textStorage -> nil");
5504        return nil;
5505    }
5506    NSAttributedString *result = [self attributedSubstringFromRange:NSMakeRange(0, UINT_MAX)];
5507
5508    LOG(TextInput, "textStorage -> \"%@\"", result ? [result string] : @"");
5509
5510    // We have to return an empty string rather than null to prevent TSM from calling -string
5511    return result ? result : [[[NSAttributedString alloc] initWithString:@""] autorelease];
5512}
5513
5514- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
5515{
5516    [self _executeSavedKeypressCommands];
5517
5518    NSWindow *window = [self window];
5519    WebFrame *frame = [self _frame];
5520
5521    if (window)
5522        thePoint = [window convertScreenToBase:thePoint];
5523    thePoint = [self convertPoint:thePoint fromView:nil];
5524
5525    DOMRange *range = [frame _characterRangeAtPoint:thePoint];
5526    if (!range) {
5527        LOG(TextInput, "characterIndexForPoint:(%f, %f) -> NSNotFound", thePoint.x, thePoint.y);
5528        return NSNotFound;
5529    }
5530
5531    unsigned result = [frame _convertDOMRangeToNSRange:range].location;
5532    LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", thePoint.x, thePoint.y, result);
5533    return result;
5534}
5535
5536- (NSRect)firstRectForCharacterRange:(NSRange)theRange
5537{
5538    [self _executeSavedKeypressCommands];
5539
5540    WebFrame *frame = [self _frame];
5541
5542    // Just to match NSTextView's behavior. Regression tests cannot detect this;
5543    // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
5544    // (type something; try ranges (1, -1) and (2, -1).
5545    if ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0))
5546        theRange.length = 0;
5547
5548    DOMRange *range = [frame _convertNSRangeToDOMRange:theRange];
5549    if (!range) {
5550        LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (0, 0, 0, 0)", theRange.location, theRange.length);
5551        return NSMakeRect(0, 0, 0, 0);
5552    }
5553
5554    ASSERT([range startContainer]);
5555    ASSERT([range endContainer]);
5556
5557    NSRect resultRect = [frame _firstRectForDOMRange:range];
5558    resultRect = [self convertRect:resultRect toView:nil];
5559
5560    NSWindow *window = [self window];
5561    if (window)
5562        resultRect.origin = [window convertBaseToScreen:resultRect.origin];
5563
5564    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);
5565    return resultRect;
5566}
5567
5568- (NSRange)selectedRange
5569{
5570    [self _executeSavedKeypressCommands];
5571
5572    if (!isTextInput(core([self _frame]))) {
5573        LOG(TextInput, "selectedRange -> (NSNotFound, 0)");
5574        return NSMakeRange(NSNotFound, 0);
5575    }
5576    NSRange result = [[self _frame] _selectedNSRange];
5577
5578    LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length);
5579    return result;
5580}
5581
5582- (NSRange)markedRange
5583{
5584    [self _executeSavedKeypressCommands];
5585
5586    WebFrame *webFrame = [self _frame];
5587    Frame* coreFrame = core(webFrame);
5588    if (!coreFrame)
5589        return NSMakeRange(0, 0);
5590    NSRange result = [webFrame _convertToNSRange:coreFrame->editor().compositionRange().get()];
5591
5592    LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length);
5593    return result;
5594}
5595
5596- (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange
5597{
5598    [self _executeSavedKeypressCommands];
5599
5600    WebFrame *frame = [self _frame];
5601    Frame* coreFrame = core(frame);
5602    if (!isTextInput(coreFrame) || isInPasswordField(coreFrame)) {
5603        LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length);
5604        return nil;
5605    }
5606    RefPtr<Range> range = [frame _convertToDOMRange:nsRange];
5607    if (!range) {
5608        LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length);
5609        return nil;
5610    }
5611
5612    NSAttributedString *result = [WebHTMLConverter editingAttributedStringFromRange:range.get()];
5613
5614    // [WebHTMLConverter editingAttributedStringFromRange:]  insists on inserting a trailing
5615    // whitespace at the end of the string which breaks the ATOK input method.  <rdar://problem/5400551>
5616    // To work around this we truncate the resultant string to the correct length.
5617    if ([result length] > nsRange.length) {
5618        ASSERT([result length] == nsRange.length + 1);
5619        ASSERT([[result string] characterAtIndex:nsRange.length] == '\n' || [[result string] characterAtIndex:nsRange.length] == ' ');
5620        result = [result attributedSubstringFromRange:NSMakeRange(0, nsRange.length)];
5621    }
5622    LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%@\"", nsRange.location, nsRange.length, [result string]);
5623    return result;
5624}
5625
5626- (NSInteger)conversationIdentifier
5627{
5628    return (NSInteger)self;
5629}
5630
5631- (BOOL)hasMarkedText
5632{
5633    Frame* coreFrame = core([self _frame]);
5634    BOOL result = coreFrame && coreFrame->editor().hasComposition();
5635
5636    if (result) {
5637        // A saved command can confirm a composition, but it cannot start a new one.
5638        [self _executeSavedKeypressCommands];
5639        result = coreFrame->editor().hasComposition();
5640    }
5641
5642    LOG(TextInput, "hasMarkedText -> %u", result);
5643    return result;
5644}
5645
5646- (void)unmarkText
5647{
5648    [self _executeSavedKeypressCommands];
5649
5650    LOG(TextInput, "unmarkText");
5651
5652    // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
5653    WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
5654
5655    if (parameters) {
5656        parameters->eventInterpretationHadSideEffects = true;
5657        parameters->consumedByIM = false;
5658    }
5659
5660    if (Frame* coreFrame = core([self _frame]))
5661        coreFrame->editor().confirmComposition();
5662}
5663
5664static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnderline>& result)
5665{
5666    int length = [[string string] length];
5667
5668    int i = 0;
5669    while (i < length) {
5670        NSRange range;
5671        NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)];
5672
5673        if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
5674            Color color = Color::black;
5675            if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName])
5676                color = colorFromNSColor([colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
5677            result.append(CompositionUnderline(range.location, NSMaxRange(range), color, [style intValue] > 1));
5678        }
5679
5680        i = range.location + range.length;
5681    }
5682}
5683
5684- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
5685{
5686    [self _executeSavedKeypressCommands];
5687
5688    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
5689    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
5690
5691    LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelRange.location, newSelRange.length);
5692
5693    // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
5694    WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
5695
5696    if (parameters) {
5697        parameters->eventInterpretationHadSideEffects = true;
5698        parameters->consumedByIM = false;
5699    }
5700
5701    Frame* coreFrame = core([self _frame]);
5702    if (!coreFrame)
5703        return;
5704
5705    if (![self _isEditable])
5706        return;
5707
5708    Vector<CompositionUnderline> underlines;
5709    NSString *text;
5710    NSRange replacementRange = { NSNotFound, 0 };
5711
5712    if (isAttributedString) {
5713        // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation.
5714        text = [string string];
5715        NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, [text length])];
5716        LOG(TextInput, "    ReplacementRange: %@", rangeString);
5717        // The AppKit adds a 'secret' property to the string that contains the replacement range.
5718        // The replacement range is the range of the the text that should be replaced with the new string.
5719        if (rangeString)
5720            replacementRange = NSRangeFromString(rangeString);
5721
5722        extractUnderlines(string, underlines);
5723    } else
5724        text = string;
5725
5726    if (replacementRange.location != NSNotFound)
5727        [[self _frame] _selectNSRange:replacementRange];
5728
5729    coreFrame->editor().setComposition(text, underlines, newSelRange.location, NSMaxRange(newSelRange));
5730}
5731
5732- (void)doCommandBySelector:(SEL)selector
5733{
5734    LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
5735
5736    // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
5737    // The same call to interpretKeyEvents can do more than one command.
5738    WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
5739    if (parameters)
5740        parameters->consumedByIM = false;
5741
5742    KeyboardEvent* event = parameters ? parameters->event : 0;
5743    bool shouldSaveCommand = parameters && parameters->shouldSaveCommands;
5744
5745    // As in insertText:, we assume that the call comes from an input method if there is marked text.
5746    RefPtr<Frame> coreFrame = core([self _frame]);
5747    bool isFromInputMethod = coreFrame && coreFrame->editor().hasComposition();
5748
5749    if (event && shouldSaveCommand && !isFromInputMethod)
5750        event->keypressCommands().append(KeypressCommand(NSStringFromSelector(selector)));
5751    else {
5752        // Make sure that only direct calls to doCommandBySelector: see the parameters by setting to 0.
5753        _private->interpretKeyEventsParameters = 0;
5754
5755        bool eventWasHandled;
5756
5757        WebView *webView = [self _webView];
5758        if ([[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector])
5759            eventWasHandled = true;
5760        else {
5761            Editor::Command command = [self coreCommandBySelector:selector];
5762            if (command.isSupported())
5763                eventWasHandled = command.execute(event);
5764            else {
5765                // If WebKit does not support this command, we need to pass the selector to super.
5766                _private->selectorForDoCommandBySelector = selector;
5767
5768                // The sink does two things: 1) Tells us if the responder went unhandled, and
5769                // 2) prevents any NSBeep; we don't ever want to beep here.
5770                WebResponderChainSink *sink = [[WebResponderChainSink alloc] initWithResponderChain:self];
5771                [super doCommandBySelector:selector];
5772                eventWasHandled = ![sink receivedUnhandledCommand];
5773                [sink detach];
5774                [sink release];
5775
5776                _private->selectorForDoCommandBySelector = 0;
5777            }
5778        }
5779
5780        if (parameters)
5781            parameters->eventInterpretationHadSideEffects |= eventWasHandled;
5782
5783        _private->interpretKeyEventsParameters = parameters;
5784    }
5785}
5786
5787- (void)insertText:(id)string
5788{
5789    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
5790    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
5791
5792    LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
5793
5794    WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
5795    if (parameters)
5796        parameters->consumedByIM = false;
5797
5798    RefPtr<Frame> coreFrame = core([self _frame]);
5799    NSString *text;
5800    NSRange replacementRange = { NSNotFound, 0 };
5801    bool isFromInputMethod = coreFrame && coreFrame->editor().hasComposition();
5802
5803    Vector<DictationAlternative> dictationAlternativeLocations;
5804    if (isAttributedString) {
5805#if USE(DICTATION_ALTERNATIVES)
5806        Vector<WebCore::TextAlternativeWithRange> textAlternatives;
5807        collectDictationTextAlternatives(string, textAlternatives);
5808        if (!textAlternatives.isEmpty())
5809            [[self _webView] _getWebCoreDictationAlternatives:dictationAlternativeLocations fromTextAlternatives:textAlternatives];
5810#endif
5811        // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
5812        // It does not look like any input methods ever use insertText: with attributes other than NSTextInputReplacementRangeAttributeName.
5813        text = [string string];
5814        NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, [text length])];
5815        LOG(TextInput, "    ReplacementRange: %@", rangeString);
5816        if (rangeString) {
5817            replacementRange = NSRangeFromString(rangeString);
5818            isFromInputMethod = true;
5819        }
5820    } else
5821        text = string;
5822
5823    KeyboardEvent* event = parameters ? parameters->event : 0;
5824
5825    // insertText can be called for several reasons:
5826    // - If it's from normal key event processing (including key bindings), we may need to save the action to perform it later.
5827    // - If it's 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.
5828    // 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.
5829    // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse),
5830    // then we also execute it immediately, as there will be no other chance.
5831    bool shouldSaveCommand = parameters && parameters->shouldSaveCommands;
5832    if (event && shouldSaveCommand && !isFromInputMethod) {
5833        event->keypressCommands().append(KeypressCommand("insertText:", text));
5834        return;
5835    }
5836
5837    if (!coreFrame || !coreFrame->editor().canEdit())
5838        return;
5839
5840    if (replacementRange.location != NSNotFound)
5841        [[self _frame] _selectNSRange:replacementRange];
5842
5843    bool eventHandled = false;
5844    String eventText = text;
5845    eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
5846    if (!coreFrame->editor().hasComposition()) {
5847        // An insertText: might be handled by other responders in the chain if we don't handle it.
5848        // One example is space bar that results in scrolling down the page.
5849
5850        if (!dictationAlternativeLocations.isEmpty())
5851            eventHandled = coreFrame->editor().insertDictatedText(eventText, dictationAlternativeLocations, event);
5852        else
5853            eventHandled = coreFrame->editor().insertText(eventText, event);
5854    } else {
5855        eventHandled = true;
5856        coreFrame->editor().confirmComposition(eventText);
5857    }
5858
5859    if (parameters)
5860        parameters->eventInterpretationHadSideEffects |= eventHandled;
5861}
5862
5863- (void)_updateSecureInputState
5864{
5865    if (![[self window] isKeyWindow] || ([[self window] firstResponder] != self && !_private->_forceUpdateSecureInputState)) {
5866        if (_private->isInSecureInputState) {
5867            DisableSecureEventInput();
5868            _private->isInSecureInputState = NO;
5869        }
5870        return;
5871    }
5872
5873    Frame* coreFrame = core([self _frame]);
5874    if (!coreFrame)
5875        return;
5876
5877    if (isInPasswordField(coreFrame)) {
5878        if (!_private->isInSecureInputState)
5879            EnableSecureEventInput();
5880        _private->isInSecureInputState = YES;
5881        // WebKit substitutes nil for input context when in password field, which corresponds to null TSMDocument. So, there is
5882        // no need to call TSMGetActiveDocument(), which may return an incorrect result when selection hasn't been yet updated
5883        // after focusing a node.
5884        static CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
5885        TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag, sizeof(CFArrayRef), &inputSources);
5886    } else {
5887        if (_private->isInSecureInputState)
5888            DisableSecureEventInput();
5889        _private->isInSecureInputState = NO;
5890        TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
5891    }
5892}
5893
5894- (void)_updateSelectionForInputManager
5895{
5896    Frame* coreFrame = core([self _frame]);
5897    if (!coreFrame)
5898        return;
5899
5900    BOOL exposeInputContext = isTextInput(coreFrame) && !isInPasswordField(coreFrame);
5901    if (exposeInputContext != _private->exposeInputContext) {
5902        _private->exposeInputContext = exposeInputContext;
5903        // Let AppKit cache a potentially changed input context.
5904        // WebCore routinely sets the selection to None when editing, and IMs become unhappy when an input context suddenly turns nil, see bug 26009.
5905        if (!coreFrame->selection()->isNone())
5906            [NSApp updateWindows];
5907    }
5908
5909    [self _updateSecureInputState];
5910
5911    if (!coreFrame->editor().hasComposition() || coreFrame->editor().ignoreCompositionSelectionChange())
5912        return;
5913
5914    unsigned start;
5915    unsigned end;
5916    if (coreFrame->editor().getCompositionSelection(start, end))
5917        [[NSInputManager currentInputManager] markedTextSelectionChanged:NSMakeRange(start, end - start) client:self];
5918    else {
5919        coreFrame->editor().cancelComposition();
5920        [[NSInputManager currentInputManager] markedTextAbandoned:self];
5921    }
5922}
5923
5924@end
5925
5926@implementation WebHTMLView (WebDocumentPrivateProtocols)
5927
5928- (NSRect)selectionRect
5929{
5930    if (![self _hasSelection])
5931        return NSZeroRect;
5932    return core([self _frame])->selection()->bounds();
5933}
5934
5935- (NSArray *)selectionTextRects
5936{
5937    if (![self _hasSelection])
5938        return nil;
5939
5940    Vector<FloatRect> list;
5941    if (Frame* coreFrame = core([self _frame]))
5942        coreFrame->selection()->getClippedVisibleTextRectangles(list);
5943
5944    size_t size = list.size();
5945
5946    NSMutableArray *result = [NSMutableArray arrayWithCapacity:size];
5947
5948    for (size_t i = 0; i < size; ++i)
5949        [result addObject:[NSValue valueWithRect:list[i]]];
5950
5951    return result;
5952}
5953
5954- (NSView *)selectionView
5955{
5956    return self;
5957}
5958
5959- (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText
5960{
5961    if (![self _hasSelection])
5962        return nil;
5963    return selectionImage(core([self _frame]), forceBlackText);
5964}
5965
5966- (NSRect)selectionImageRect
5967{
5968    if (![self _hasSelection])
5969        return NSZeroRect;
5970    return core([self _frame])->selection()->bounds();
5971}
5972
5973- (NSArray *)pasteboardTypesForSelection
5974{
5975    if ([self _canSmartCopyOrDelete]) {
5976        NSMutableArray *types = [[[[self class] _selectionPasteboardTypes] mutableCopy] autorelease];
5977        [types addObject:WebSmartPastePboardType];
5978        return types;
5979    } else {
5980        return [[self class] _selectionPasteboardTypes];
5981    }
5982}
5983
5984- (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard
5985{
5986    [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:nil];
5987}
5988
5989- (void)selectAll
5990{
5991    Frame* coreFrame = core([self _frame]);
5992    if (coreFrame)
5993        coreFrame->selection()->selectAll();
5994}
5995
5996- (void)deselectAll
5997{
5998    Frame* coreFrame = core([self _frame]);
5999    if (!coreFrame)
6000        return;
6001    coreFrame->selection()->clear();
6002}
6003
6004- (NSString *)string
6005{
6006    return [[self _frame] _stringForRange:[self _documentRange]];
6007}
6008
6009- (NSAttributedString *)_attributeStringFromDOMRange:(DOMRange *)range
6010{
6011    NSAttributedString *attributedString;
6012#if !LOG_DISABLED
6013    double start = CFAbsoluteTimeGetCurrent();
6014#endif
6015    attributedString = [[[NSAttributedString alloc] _initWithDOMRange:range] autorelease];
6016#if !LOG_DISABLED
6017    double duration = CFAbsoluteTimeGetCurrent() - start;
6018    LOG(Timing, "creating attributed string from selection took %f seconds.", duration);
6019#endif
6020    return attributedString;
6021}
6022
6023- (NSAttributedString *)attributedString
6024{
6025    DOMDocument *document = [[self _frame] DOMDocument];
6026    NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[document _documentRange]];
6027    if (!attributedString) {
6028        Document* coreDocument = core(document);
6029        attributedString = [WebHTMLConverter editingAttributedStringFromRange:Range::create(coreDocument, coreDocument, 0, coreDocument, coreDocument->childNodeCount()).get()];
6030    }
6031    return attributedString;
6032}
6033
6034- (NSString *)selectedString
6035{
6036    return [[self _frame] _selectedString];
6037}
6038
6039- (NSAttributedString *)selectedAttributedString
6040{
6041    NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[self _selectedRange]];
6042    if (!attributedString) {
6043        Frame* coreFrame = core([self _frame]);
6044        if (coreFrame) {
6045            RefPtr<Range> range = coreFrame->selection()->selection().toNormalizedRange();
6046            attributedString = [WebHTMLConverter editingAttributedStringFromRange:range.get()];
6047        }
6048    }
6049    return attributedString;
6050}
6051
6052- (BOOL)supportsTextEncoding
6053{
6054    return YES;
6055}
6056
6057- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection
6058{
6059    return [self _findString:string options:(forward ? 0 : WebFindOptionsBackwards) | (caseFlag ? 0 : WebFindOptionsCaseInsensitive) | (wrapFlag ? WebFindOptionsWrapAround : 0) | (startInSelection ? WebFindOptionsStartInSelection : 0)];
6060}
6061
6062@end
6063
6064@implementation WebHTMLView (WebDocumentInternalProtocols)
6065
6066- (NSDictionary *)elementAtPoint:(NSPoint)point
6067{
6068    return [self elementAtPoint:point allowShadowContent:NO];
6069}
6070
6071- (NSDictionary *)elementAtPoint:(NSPoint)point allowShadowContent:(BOOL)allow
6072{
6073    Frame* coreFrame = core([self _frame]);
6074    if (!coreFrame)
6075        return nil;
6076    HitTestRequest::HitTestRequestType hitType = HitTestRequest::ReadOnly | HitTestRequest::Active
6077        | (allow ? 0 : HitTestRequest::DisallowShadowContent);
6078    return [[[WebElementDictionary alloc] initWithHitTestResult:coreFrame->eventHandler()->hitTestResultAtPoint(IntPoint(point), hitType)] autorelease];
6079}
6080
6081- (NSUInteger)countMatchesForText:(NSString *)string inDOMRange:(DOMRange *)range options:(WebFindOptions)options limit:(NSUInteger)limit markMatches:(BOOL)markMatches
6082{
6083    Frame* coreFrame = core([self _frame]);
6084    if (!coreFrame)
6085        return 0;
6086
6087    return coreFrame->editor().countMatchesForText(string, core(range), coreOptions(options), limit, markMatches, 0);
6088}
6089
6090- (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue
6091{
6092    Frame* coreFrame = core([self _frame]);
6093    if (!coreFrame)
6094        return;
6095    coreFrame->editor().setMarkedTextMatchesAreHighlighted(newValue);
6096}
6097
6098- (BOOL)markedTextMatchesAreHighlighted
6099{
6100    Frame* coreFrame = core([self _frame]);
6101    return coreFrame && coreFrame->editor().markedTextMatchesAreHighlighted();
6102}
6103
6104- (void)unmarkAllTextMatches
6105{
6106    Frame* coreFrame = core([self _frame]);
6107    if (!coreFrame)
6108        return;
6109    Document* document = coreFrame->document();
6110    if (!document)
6111        return;
6112    document->markers()->removeMarkers(DocumentMarker::TextMatch);
6113}
6114
6115- (NSArray *)rectsForTextMatches
6116{
6117    Frame* coreFrame = core([self _frame]);
6118    if (!coreFrame)
6119        return [NSArray array];
6120    Document* document = coreFrame->document();
6121    if (!document)
6122        return [NSArray array];
6123
6124    Vector<IntRect> rects = document->markers()->renderedRectsForMarkers(DocumentMarker::TextMatch);
6125    unsigned count = rects.size();
6126    NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
6127    for (unsigned index = 0; index < count; ++index)
6128        [result addObject:[NSValue valueWithRect:rects[index]]];
6129    return result;
6130}
6131
6132- (BOOL)_findString:(NSString *)string options:(WebFindOptions)options
6133{
6134    if (![string length])
6135        return NO;
6136    Frame* coreFrame = core([self _frame]);
6137    return coreFrame && coreFrame->editor().findString(string, coreOptions(options));
6138}
6139
6140@end
6141
6142// This is used by AppKit and is included here so that WebDataProtocolScheme is only defined once.
6143@implementation NSURL (WebDataURL)
6144
6145+ (NSURL *)_web_uniqueWebDataURL
6146{
6147    CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault);
6148    NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef);
6149    CFRelease(UUIDRef);
6150    NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", WebDataProtocolScheme, UUIDString]];
6151    CFRelease(UUIDString);
6152    return URL;
6153}
6154
6155@end
6156
6157@implementation WebResponderChainSink
6158
6159- (id)initWithResponderChain:(NSResponder *)chain
6160{
6161    self = [super init];
6162    _lastResponderInChain = chain;
6163    while (NSResponder *next = [_lastResponderInChain nextResponder])
6164        _lastResponderInChain = next;
6165    [_lastResponderInChain setNextResponder:self];
6166    return self;
6167}
6168
6169- (void)detach
6170{
6171    [_lastResponderInChain setNextResponder:nil];
6172    _lastResponderInChain = nil;
6173}
6174
6175- (BOOL)receivedUnhandledCommand
6176{
6177    return _receivedUnhandledCommand;
6178}
6179
6180- (void)noResponderFor:(SEL)selector
6181{
6182    _receivedUnhandledCommand = YES;
6183}
6184
6185- (void)doCommandBySelector:(SEL)selector
6186{
6187    _receivedUnhandledCommand = YES;
6188}
6189
6190- (BOOL)tryToPerform:(SEL)action with:(id)object
6191{
6192    _receivedUnhandledCommand = YES;
6193    return YES;
6194}
6195
6196@end
6197