62c7383be27ac23f4dd6cfc74ebd68d9f2c49e6d
[WebKit-https.git] / WebKit / WebView.subproj / WebHTMLView.m
1 /*
2     WebHTMLView.m
3     Copyright 2002, Apple, Inc. All rights reserved.
4 */
5
6 #import <WebKit/WebHTMLView.h>
7
8 #import <WebKit/DOM.h>
9 #import <WebKit/DOMExtensions.h>
10 #import <WebKit/WebArchive.h>
11 #import <WebKit/WebBridge.h>
12 #import <WebKit/WebClipView.h>
13 #import <WebKit/WebDataProtocol.h>
14 #import <WebKit/WebDataSourcePrivate.h>
15 #import <WebKit/WebDocumentInternal.h>
16 #import <WebKit/WebDOMOperationsPrivate.h>
17 #import <WebKit/WebEditingDelegate.h>
18 #import <WebKit/WebException.h>
19 #import <WebKit/WebFramePrivate.h>
20 #import <WebKit/WebFrameViewInternal.h>
21 #import <WebKit/WebHTMLViewInternal.h>
22 #import <WebKit/WebHTMLRepresentationPrivate.h>
23 #import <WebKit/WebImageRenderer.h>
24 #import <WebKit/WebImageRendererFactory.h>
25 #import <WebKit/WebKitLogging.h>
26 #import <WebKit/WebKitNSStringExtras.h>
27 #import <WebKit/WebNetscapePluginEmbeddedView.h>
28 #import <WebKit/WebNSEventExtras.h>
29 #import <WebKit/WebNSImageExtras.h>
30 #import <WebKit/WebNSObjectExtras.h>
31 #import <WebKit/WebNSPasteboardExtras.h>
32 #import <WebKit/WebNSPrintOperationExtras.h>
33 #import <WebKit/WebNSURLExtras.h>
34 #import <WebKit/WebNSViewExtras.h>
35 #import <WebKit/WebPluginController.h>
36 #import <WebKit/WebPreferences.h>
37 #import <WebKit/WebResourcePrivate.h>
38 #import <WebKit/WebStringTruncator.h>
39 #import <WebKit/WebTextRenderer.h>
40 #import <WebKit/WebTextRendererFactory.h>
41 #import <WebKit/WebUIDelegatePrivate.h>
42 #import <WebKit/WebUnicode.h>
43 #import <WebKit/WebViewInternal.h>
44 #import <WebKit/WebViewPrivate.h>
45
46 #import <AppKit/NSAccessibility.h>
47 #import <AppKit/NSGraphicsContextPrivate.h>
48 #import <AppKit/NSResponder_Private.h>
49
50 #import <Foundation/NSFileManager_NSURLExtras.h>
51 #import <Foundation/NSURL_NSURLExtras.h>
52 #import <Foundation/NSURLFileTypeMappings.h>
53
54 #import <CoreGraphics/CGContextGState.h>
55
56 // Included to help work around this bug:
57 // <rdar://problem/3630640>: "Calling interpretKeyEvents: in a custom text view can fail to process keys right after app startup"
58 #import <AppKit/NSKeyBindingManager.h>
59
60 // Kill ring calls. Would be better to use NSKillRing.h, but that's not available in SPI.
61 void _NSInitializeKillRing(void);
62 void _NSAppendToKillRing(NSString *);
63 void _NSPrependToKillRing(NSString *);
64 NSString *_NSYankFromKillRing(void);
65 NSString *_NSYankPreviousFromKillRing(void);
66 void _NSNewKillRingSequence(void);
67 void _NSSetKillRingToYankedState(void);
68 void _NSResetKillRingOperationFlag(void);
69
70 @interface NSView (AppKitSecretsIKnowAbout)
71 - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
72 - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
73 - (NSRect)_dirtyRect;
74 - (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants;
75 @end
76
77 @interface NSApplication (AppKitSecretsIKnowAbout)
78 - (void)speakString:(NSString *)string;
79 @end
80
81 @interface NSWindow (AppKitSecretsIKnowAbout)
82 - (id)_newFirstResponderAfterResigning;
83 @end
84
85 @interface NSAttributedString (AppKitSecretsIKnowAbout)
86 - (id)_initWithDOMRange:(DOMRange *)domRange;
87 - (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
88 @end
89
90 @interface NSSpellChecker (CurrentlyPrivateForTextView)
91 - (void)learnWord:(NSString *)word;
92 @end
93
94 // By imaging to a width a little wider than the available pixels,
95 // thin pages will be scaled down a little, matching the way they
96 // print in IE and Camino. This lets them use fewer sheets than they
97 // would otherwise, which is presumably why other browsers do this.
98 // Wide pages will be scaled down more than this.
99 #define PrintingMinimumShrinkFactor     1.25
100
101 // This number determines how small we are willing to reduce the page content
102 // in order to accommodate the widest line. If the page would have to be
103 // reduced smaller to make the widest line fit, we just clip instead (this
104 // behavior matches MacIE and Mozilla, at least)
105 #define PrintingMaximumShrinkFactor     2.0
106
107 #define AUTOSCROLL_INTERVAL             0.1
108
109 #define DRAG_LABEL_BORDER_X             4.0
110 #define DRAG_LABEL_BORDER_Y             2.0
111 #define DRAG_LABEL_RADIUS               5.0
112 #define DRAG_LABEL_BORDER_Y_OFFSET              2.0
113
114 #define MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP        120.0
115 #define MAX_DRAG_LABEL_WIDTH                    320.0
116
117 #define DRAG_LINK_LABEL_FONT_SIZE   11.0
118 #define DRAG_LINK_URL_FONT_SIZE   10.0
119
120 #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_3
121 #define USE_APPKIT_FOR_ATTRIBUTED_STRINGS
122 #endif
123
124 // Any non-zero value will do, but using something recognizable might help us debug some day.
125 #define TRACKING_RECT_TAG 0xBADFACE
126
127 // FIXME: This constant is copied from AppKit's _NXSmartPaste constant.
128 #define WebSmartPastePboardType @"NeXT smart paste pasteboard type"
129
130 static BOOL forceRealHitTest = NO;
131
132 @interface WebHTMLView (WebTextSizing) <_web_WebDocumentTextSizing>
133 @end
134
135 @interface WebHTMLView (WebHTMLViewFileInternal)
136 - (BOOL)_imageExistsAtPaths:(NSArray *)paths;
137 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
138 - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
139 - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
140 - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action;
141 - (float)_calculatePrintHeight;
142 - (void)_updateTextSizeMultiplier;
143 - (DOMRange *)_selectedRange;
144 - (BOOL)_shouldDeleteRange:(DOMRange *)range;
145 - (void)_deleteRange:(DOMRange *)range 
146            preflight:(BOOL)preflight 
147             killRing:(BOOL)killRing 
148              prepend:(BOOL)prepend 
149        smartDeleteOK:(BOOL)smartDeleteOK;
150 - (void)_deleteSelection;
151 - (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard;
152 @end
153
154 @interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in a normal category and stop doing the forward declaration trick.
155 - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize;
156 @end
157
158 @interface WebHTMLView (WebNSTextInputSupport) <NSTextInput>
159 - (void)_updateSelectionForInputManager;
160 - (void)_insertText:(NSString *)text selectInsertedText:(BOOL)selectText;
161 @end
162
163 @interface NSView (WebHTMLViewFileInternal)
164 - (void)_web_setPrintingModeRecursive;
165 - (void)_web_clearPrintingModeRecursive;
166 - (void)_web_layoutIfNeededRecursive:(NSRect)rect testDirtyRect:(bool)testDirtyRect;
167 @end
168
169 @interface NSMutableDictionary (WebHTMLViewFileInternal)
170 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key;
171 @end
172
173 // Handles the complete: text command
174 @interface WebTextCompleteController : NSObject
175 {
176 @private
177     WebHTMLView *_view;
178     NSWindow *_popupWindow;
179     NSTableView *_tableView;
180     NSArray *_completions;
181     NSString *_originalString;
182     int prefixLength;
183 }
184 - (id)initWithHTMLView:(WebHTMLView *)view;
185 - (void)doCompletion;
186 - (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft;
187 - (BOOL)filterKeyDown:(NSEvent *)event;
188 - (void)_reflectSelection;
189 @end
190
191 @implementation WebHTMLViewPrivate
192
193 - (void)dealloc
194 {
195     ASSERT(autoscrollTimer == nil);
196     ASSERT(autoscrollTriggerEvent == nil);
197     
198     [mouseDownEvent release];
199     [draggingImageURL release];
200     [pluginController release];
201     [toolTip release];
202     [compController release];
203
204     [super dealloc];
205 }
206
207 @end
208
209 @implementation WebHTMLView (WebHTMLViewFileInternal)
210
211 - (BOOL)_imageExistsAtPaths:(NSArray *)paths
212 {
213     NSURLFileTypeMappings *mappings = [NSURLFileTypeMappings sharedMappings];
214     NSArray *imageMIMETypes = [[WebImageRendererFactory sharedFactory] supportedMIMETypes];
215     NSEnumerator *enumerator = [paths objectEnumerator];
216     NSString *path;
217     
218     while ((path = [enumerator nextObject]) != nil) {
219         NSString *MIMEType = [mappings MIMETypeForExtension:[path pathExtension]];
220         if ([imageMIMETypes containsObject:MIMEType]) {
221             return YES;
222         }
223     }
224     
225     return NO;
226 }
227
228 - (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths
229 {
230     DOMDocumentFragment *fragment = [[[self _bridge] DOMDocument] createDocumentFragment];
231     NSURLFileTypeMappings *mappings = [NSURLFileTypeMappings sharedMappings];
232     NSArray *imageMIMETypes = [[WebImageRendererFactory sharedFactory] supportedMIMETypes];
233     NSEnumerator *enumerator = [paths objectEnumerator];
234     WebDataSource *dataSource = [self _dataSource];
235     NSString *path;
236     
237     while ((path = [enumerator nextObject]) != nil) {
238         NSString *MIMEType = [mappings MIMETypeForExtension:[path pathExtension]];
239         if ([imageMIMETypes containsObject:MIMEType]) {
240             WebResource *resource = [[WebResource alloc] initWithData:[NSData dataWithContentsOfFile:path]
241                                                                   URL:[NSURL fileURLWithPath:path]
242                                                              MIMEType:MIMEType 
243                                                      textEncodingName:nil
244                                                             frameName:nil];
245             if (resource) {
246                 [fragment appendChild:[dataSource _imageElementWithImageResource:resource]];
247                 [resource release];
248             }
249         }
250     }
251     
252     return [fragment firstChild] != nil ? fragment : nil;
253 }
254
255 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText
256 {
257     NSArray *types = [pasteboard types];
258
259     if ([types containsObject:WebArchivePboardType]) {
260         WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]];
261         if (archive) {
262             DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithArchive:archive];
263             [archive release];
264             if (fragment) {
265                 return fragment;
266             }
267         }
268     }
269     
270     if ([types containsObject:NSFilenamesPboardType]) {
271         DOMDocumentFragment *fragment = [self _documentFragmentWithPaths:[pasteboard propertyListForType:NSFilenamesPboardType]];
272         if (fragment != nil) {
273             return fragment;
274         }
275     }
276     
277     NSURL *URL;
278     
279     if ([types containsObject:NSHTMLPboardType]) {
280         NSString *HTMLString = [pasteboard stringForType:NSHTMLPboardType];
281         // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785.
282         if ([HTMLString hasPrefix:@"Version:"]) {
283             NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch];
284             if (range.location != NSNotFound) {
285                 HTMLString = [HTMLString substringFromIndex:range.location];
286             }
287         }
288         if ([HTMLString length] != 0) {
289             return [[self _bridge] documentFragmentWithMarkupString:HTMLString baseURLString:nil];
290         }
291     }
292     
293     if ([types containsObject:NSTIFFPboardType]) {
294         WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSTIFFPboardType]
295                                                               URL:[NSURL _web_uniqueWebDataURLWithRelativeString:@"/image.tiff"]
296                                                          MIMEType:@"image/tiff" 
297                                                  textEncodingName:nil
298                                                         frameName:nil];
299         DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
300         [resource release];
301         return fragment;
302     }
303     
304     if ([types containsObject:NSPICTPboardType]) {
305         WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPICTPboardType]
306                                                               URL:[NSURL _web_uniqueWebDataURLWithRelativeString:@"/image.pict"]
307                                                          MIMEType:@"image/pict" 
308                                                  textEncodingName:nil
309                                                         frameName:nil];
310         DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
311         [resource release];
312         return fragment;
313     }
314     
315 #ifdef USE_APPKIT_FOR_ATTRIBUTED_STRINGS
316     NSAttributedString *string = nil;
317     if ([types containsObject:NSRTFDPboardType]) {
318         string = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
319     }
320     if (string == nil && [types containsObject:NSRTFPboardType]) {
321         string = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
322     }
323     if (string != nil) {
324         NSArray *elements = [[NSArray alloc] initWithObjects:@"style", nil];
325         NSDictionary *documentAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:elements, NSExcludedElementsDocumentAttribute, nil];
326         [elements release];
327         NSArray *subresources;
328         DOMDocumentFragment *fragment = [string _documentFromRange:NSMakeRange(0, [string length]) 
329                                                           document:[[self _bridge] DOMDocument] 
330                                                 documentAttributes:documentAttributes
331                                                       subresources:&subresources];
332         [documentAttributes release];
333         [string release];
334         if (fragment) {
335             if ([subresources count] != 0) {
336                 [[self _dataSource] _addSubresources:subresources];
337             }
338             return fragment;
339         }
340     }
341 #endif
342     
343     if ((URL = [NSURL URLFromPasteboard:pasteboard])) {
344         NSString *URLString = [URL _web_userVisibleString];
345         if ([URLString length] > 0) {
346             return [[self _bridge] documentFragmentWithText:URLString];
347         }
348     }
349     
350     if (allowPlainText && [types containsObject:NSStringPboardType]) {
351         return [[self _bridge] documentFragmentWithText:[pasteboard stringForType:NSStringPboardType]];
352     }
353     
354     return nil;
355 }
356
357 - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText
358 {
359     DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard allowPlainText:allowPlainText];
360     WebBridge *bridge = [self _bridge];
361     if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[self _selectedRange] givenAction:WebViewInsertActionPasted]) {
362         [bridge replaceSelectionWithFragment:fragment selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]];
363     }
364 }
365
366 - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
367 {
368     WebView *webView = [self _webView];
369     DOMNode *child = [fragment firstChild];
370     if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]]) {
371         return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action];
372     } else {
373         return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action];
374     }
375 }
376
377 - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action
378 {
379     WebView *webView = [self _webView];
380     DOMRange *selectedRange = [self _selectedRange];
381     return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:selectedRange givenAction:action];
382 }
383
384 // Calculate the vertical size of the view that fits on a single page
385 - (float)_calculatePrintHeight
386 {
387     // Obtain the print info object for the current operation
388     NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo];
389     
390     // Calculate the page height in points
391     NSSize paperSize = [pi paperSize];
392     return paperSize.height - [pi topMargin] - [pi bottomMargin];
393 }
394
395 - (void)_updateTextSizeMultiplier
396 {
397     [[self _bridge] setTextSizeMultiplier:[[self _webView] textSizeMultiplier]];    
398 }
399
400 - (DOMRange *)_selectedRange
401 {
402     return [[self _bridge] selectedDOMRange];
403 }
404
405 - (BOOL)_shouldDeleteRange:(DOMRange *)range
406 {
407     if (range == nil || [range collapsed])
408         return NO;
409     WebView *webView = [self _webView];
410     return [[webView _editingDelegateForwarder] webView:webView shouldDeleteDOMRange:range];
411 }
412
413 - (void)_deleteRange:(DOMRange *)range 
414            preflight:(BOOL)preflight 
415             killRing:(BOOL)killRing 
416              prepend:(BOOL)prepend 
417        smartDeleteOK:(BOOL)smartDeleteOK 
418 {
419     if (![self _shouldDeleteRange:range]) {
420         return;
421     }
422     WebBridge *bridge = [self _bridge];
423     if (killRing && _private->startNewKillRingSequence) {
424         _NSNewKillRingSequence();
425     }
426     [bridge setSelectedDOMRange:range affinity:NSSelectionAffinityUpstream];
427     if (killRing) {
428         if (prepend) {
429             _NSPrependToKillRing([bridge selectedString]);
430         } else {
431             _NSAppendToKillRing([bridge selectedString]);
432         }
433         _private->startNewKillRingSequence = NO;
434     }
435     BOOL smartDelete = smartDeleteOK ? [self _canSmartCopyOrDelete] : NO;
436     [bridge deleteSelectionWithSmartDelete:smartDelete];
437 }
438
439 - (void)_deleteSelection
440 {
441     [self _deleteRange:[self _selectedRange]
442              preflight:YES 
443               killRing:YES 
444                prepend:NO
445          smartDeleteOK:YES];
446 }
447
448 - (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard
449 {
450     return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType];
451 }
452
453 @end
454
455 @implementation WebHTMLView (WebPrivate)
456
457 - (void)_reset
458 {
459     [WebImageRenderer stopAnimationsInView:self];
460 }
461
462 - (WebView *)_webView
463 {
464     // We used to use the view hierarchy exclusively here, but that won't work
465     // right when the first viewDidMoveToSuperview call is done, and this wil.
466     return [[self _frame] webView];
467 }
468
469 - (WebFrame *)_frame
470 {
471     WebFrameView *webFrameView = [self _web_parentWebFrameView];
472     return [webFrameView webFrame];
473 }
474
475 // Required so view can access the part's selection.
476 - (WebBridge *)_bridge
477 {
478     return [[self _frame] _bridge];
479 }
480
481 - (WebDataSource *)_dataSource
482 {
483     return [[self _frame] dataSource];
484 }
485
486 + (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent
487 {
488     NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
489         location:[[flagsChangedEvent window] convertScreenToBase:[NSEvent mouseLocation]]
490         modifierFlags:[flagsChangedEvent modifierFlags]
491         timestamp:[flagsChangedEvent timestamp]
492         windowNumber:[flagsChangedEvent windowNumber]
493         context:[flagsChangedEvent context]
494         eventNumber:0 clickCount:0 pressure:0];
495
496     // Pretend it's a mouse move.
497     [[NSNotificationCenter defaultCenter]
498         postNotificationName:NSMouseMovedNotification object:self
499         userInfo:[NSDictionary dictionaryWithObject:fakeEvent forKey:@"NSEvent"]];
500 }
501
502 - (void)_updateMouseoverWithFakeEvent
503 {
504     NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
505         location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
506         modifierFlags:[[NSApp currentEvent] modifierFlags]
507         timestamp:[NSDate timeIntervalSinceReferenceDate]
508         windowNumber:[[self window] windowNumber]
509         context:[[NSApp currentEvent] context]
510         eventNumber:0 clickCount:0 pressure:0];
511     
512     [self _updateMouseoverWithEvent:fakeEvent];
513 }
514
515 - (void)_frameOrBoundsChanged
516 {
517     if (!NSEqualSizes(_private->lastLayoutSize, [(NSClipView *)[self superview] documentVisibleRect].size)) {
518         [self setNeedsLayout:YES];
519         [self setNeedsDisplay:YES];
520         [_private->compController endRevertingChange:NO moveLeft:NO];
521     }
522
523     NSPoint origin = [[self superview] bounds].origin;
524     if (!NSEqualPoints(_private->lastScrollPosition, origin)) {
525         [[self _bridge] sendScrollEvent];
526         [_private->compController endRevertingChange:NO moveLeft:NO];
527     }
528     _private->lastScrollPosition = origin;
529
530     SEL selector = @selector(_updateMouseoverWithFakeEvent);
531     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector object:nil];
532     [self performSelector:selector withObject:nil afterDelay:0];
533 }
534
535 - (void)_setAsideSubviews
536 {
537     ASSERT(!_private->subviewsSetAside);
538     ASSERT(_private->savedSubviews == nil);
539     _private->savedSubviews = _subviews;
540     _subviews = nil;
541     _private->subviewsSetAside = YES;
542  }
543  
544  - (void)_restoreSubviews
545  {
546     ASSERT(_private->subviewsSetAside);
547     ASSERT(_subviews == nil);
548     _subviews = _private->savedSubviews;
549     _private->savedSubviews = nil;
550     _private->subviewsSetAside = NO;
551 }
552
553 // Don't let AppKit even draw subviews. We take care of that.
554 - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView
555 {
556     // This helps when we print as part of a larger print process.
557     // If the WebHTMLView itself is what we're printing, then we will never have to do this.
558     BOOL wasInPrintingMode = _private->printing;
559     BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
560     if (wasInPrintingMode != isPrinting) {
561         if (isPrinting) {
562             [self _web_setPrintingModeRecursive];
563         } else {
564             [self _web_clearPrintingModeRecursive];
565         }
566     }
567
568     [self _web_layoutIfNeededRecursive: rect testDirtyRect:YES];
569
570     [self _setAsideSubviews];
571     [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect
572         rectIsVisibleRectForView:visibleView topView:topView];
573     [self _restoreSubviews];
574
575     if (wasInPrintingMode != isPrinting) {
576         if (wasInPrintingMode) {
577             [self _web_setPrintingModeRecursive];
578         } else {
579             [self _web_clearPrintingModeRecursive];
580         }
581     }
582 }
583
584 // Don't let AppKit even draw subviews. We take care of that.
585 - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect
586 {
587     BOOL needToSetAsideSubviews = !_private->subviewsSetAside;
588
589     BOOL wasInPrintingMode = _private->printing;
590     BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
591
592     if (needToSetAsideSubviews) {
593         // This helps when we print as part of a larger print process.
594         // If the WebHTMLView itself is what we're printing, then we will never have to do this.
595         if (wasInPrintingMode != isPrinting) {
596             if (isPrinting) {
597                 [self _web_setPrintingModeRecursive];
598             } else {
599                 [self _web_clearPrintingModeRecursive];
600             }
601         }
602
603         [self _web_layoutIfNeededRecursive: visRect testDirtyRect:NO];
604
605         [self _setAsideSubviews];
606     }
607     
608     [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect];
609     
610     if (needToSetAsideSubviews) {
611         if (wasInPrintingMode != isPrinting) {
612             if (wasInPrintingMode) {
613                 [self _web_setPrintingModeRecursive];
614             } else {
615                 [self _web_clearPrintingModeRecursive];
616             }
617         }
618
619         [self _restoreSubviews];
620     }
621 }
622
623 - (BOOL)_insideAnotherHTMLView
624 {
625     NSView *view = self;
626     while ((view = [view superview])) {
627         if ([view isKindOfClass:[WebHTMLView class]]) {
628             return YES;
629         }
630     }
631     return NO;
632 }
633
634 - (void)scrollPoint:(NSPoint)point
635 {
636     // Since we can't subclass NSTextView to do what we want, we have to second guess it here.
637     // If we get called during the handling of a key down event, we assume the call came from
638     // NSTextView, and ignore it and use our own code to decide how to page up and page down
639     // We are smarter about how far to scroll, and we have "superview scrolling" logic.
640     NSEvent *event = [[self window] currentEvent];
641     if ([event type] == NSKeyDown) {
642         const unichar pageUp = NSPageUpFunctionKey;
643         if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageUp length:1]].length == 1) {
644             [self tryToPerform:@selector(scrollPageUp:) with:nil];
645             return;
646         }
647         const unichar pageDown = NSPageDownFunctionKey;
648         if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageDown length:1]].length == 1) {
649             [self tryToPerform:@selector(scrollPageDown:) with:nil];
650             return;
651         }
652     }
653     
654     [super scrollPoint:point];
655 }
656
657 - (NSView *)hitTest:(NSPoint)point
658 {
659     // WebHTMLView objects handle all left mouse clicks for objects inside them.
660     // That does not include left mouse clicks with the control key held down.
661     BOOL captureHitsOnSubviews;
662     if (forceRealHitTest) {
663         captureHitsOnSubviews = NO;
664     } else {
665         NSEvent *event = [[self window] currentEvent];
666         captureHitsOnSubviews = [event type] == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask) == 0;
667     }
668     if (!captureHitsOnSubviews) {
669         return [super hitTest:point];
670     }
671     if ([[self superview] mouse:point inRect:[self frame]]) {
672         return self;
673     }
674     return nil;
675 }
676
677 static WebHTMLView *lastHitView = nil;
678
679 - (void)_clearLastHitViewIfSelf
680 {
681     if (lastHitView == self) {
682         lastHitView = nil;
683     }
684 }
685
686 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside
687 {
688     ASSERT(_private->trackingRectOwner == nil);
689     _private->trackingRectOwner = owner;
690     _private->trackingRectUserData = data;
691     return TRACKING_RECT_TAG;
692 }
693
694 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag
695 {
696     ASSERT(tag == TRACKING_RECT_TAG);
697     return [self addTrackingRect:rect owner:owner userData:data assumeInside:assumeInside];
698 }
699
700 - (void)removeTrackingRect:(NSTrackingRectTag)tag
701 {
702     ASSERT(tag == TRACKING_RECT_TAG);
703     if (_private != nil) {
704         _private->trackingRectOwner = nil;
705     }
706 }
707
708 - (void)_sendToolTipMouseExited
709 {
710     // Nothing matters except window, trackingNumber, and userData.
711     NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
712         location:NSMakePoint(0, 0)
713         modifierFlags:0
714         timestamp:0
715         windowNumber:[[self window] windowNumber]
716         context:NULL
717         eventNumber:0
718         trackingNumber:TRACKING_RECT_TAG
719         userData:_private->trackingRectUserData];
720     [_private->trackingRectOwner mouseExited:fakeEvent];
721 }
722
723 - (void)_sendToolTipMouseEntered
724 {
725     // Nothing matters except window, trackingNumber, and userData.
726     NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
727         location:NSMakePoint(0, 0)
728         modifierFlags:0
729         timestamp:0
730         windowNumber:[[self window] windowNumber]
731         context:NULL
732         eventNumber:0
733         trackingNumber:TRACKING_RECT_TAG
734         userData:_private->trackingRectUserData];
735     [_private->trackingRectOwner mouseEntered:fakeEvent];
736 }
737
738 - (void)_setToolTip:(NSString *)string
739 {
740     NSString *toolTip = [string length] == 0 ? nil : string;
741     NSString *oldToolTip = _private->toolTip;
742     if ((toolTip == nil || oldToolTip == nil) ? toolTip == oldToolTip : [toolTip isEqualToString:oldToolTip]) {
743         return;
744     }
745     if (oldToolTip) {
746         [self _sendToolTipMouseExited];
747         [oldToolTip release];
748     }
749     _private->toolTip = [toolTip copy];
750     if (toolTip) {
751         [self removeAllToolTips];
752         NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
753         [self addToolTipRect:wideOpenRect owner:self userData:NULL];
754         [self _sendToolTipMouseEntered];
755     }
756 }
757
758 - (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
759 {
760     return [[_private->toolTip copy] autorelease];
761 }
762
763 - (void)_updateMouseoverWithEvent:(NSEvent *)event
764 {
765     WebHTMLView *view = nil;
766     if ([event window] == [self window]) {
767         forceRealHitTest = YES;
768         NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]];
769         forceRealHitTest = NO;
770         while (hitView) {
771             if ([hitView isKindOfClass:[WebHTMLView class]]) {
772                 view = (WebHTMLView *)hitView;
773                 break;
774             }
775             hitView = [hitView superview];
776         }
777     }
778
779     if (lastHitView != view && lastHitView != nil) {
780         // If we are moving out of a view (or frame), let's pretend the mouse moved
781         // all the way out of that view. But we have to account for scrolling, because
782         // khtml doesn't understand our clipping.
783         NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect];
784         float yScroll = visibleRect.origin.y;
785         float xScroll = visibleRect.origin.x;
786
787         event = [NSEvent mouseEventWithType:NSMouseMoved
788                          location:NSMakePoint(-1 - xScroll, -1 - yScroll )
789                          modifierFlags:[[NSApp currentEvent] modifierFlags]
790                          timestamp:[NSDate timeIntervalSinceReferenceDate]
791                          windowNumber:[[self window] windowNumber]
792                          context:[[NSApp currentEvent] context]
793                          eventNumber:0 clickCount:0 pressure:0];
794         [[lastHitView _bridge] mouseMoved:event];
795     }
796
797     lastHitView = view;
798     
799     NSDictionary *element;
800     if (view == nil) {
801         element = nil;
802     } else {
803         [[view _bridge] mouseMoved:event];
804
805         NSPoint point = [view convertPoint:[event locationInWindow] fromView:nil];
806         element = [view elementAtPoint:point];
807     }
808
809     // Have the web view send a message to the delegate so it can do status bar display.
810     [[self _webView] _mouseDidMoveOverElement:element modifierFlags:[event modifierFlags]];
811
812     // Set a tool tip; it won't show up right away but will if the user pauses.
813     [self _setToolTip:[element objectForKey:WebCoreElementTitleKey]];
814 }
815
816 + (NSArray *)_insertablePasteboardTypes
817 {
818     static NSArray *types = nil;
819     if (!types) {
820         types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType,
821             NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSURLPboardType, 
822             NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
823     }
824     return types;
825 }
826
827 + (NSArray *)_selectionPasteboardTypes
828 {
829     // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423).
830     return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFPboardType, NSRTFDPboardType, NSStringPboardType, nil];
831 }
832
833 - (WebArchive *)_selectedArchive
834 {
835     NSArray *nodes;
836     NSString *markupString = [[self _bridge] markupStringFromRange:[self _selectedRange] nodes:&nodes];
837     return [[self _dataSource] _archiveWithMarkupString:markupString nodes:nodes];
838 }
839
840 - (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard
841 {
842     ASSERT([self _hasSelection]);
843     NSArray *types = [self pasteboardTypesForSelection];
844     [pasteboard declareTypes:types owner:nil];
845     [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard];
846 }
847
848 - (NSImage *)_dragImageForLinkElement:(NSDictionary *)element
849 {
850     NSURL *linkURL = [element objectForKey: WebElementLinkURLKey];
851
852     BOOL drawURLString = YES;
853     BOOL clipURLString = NO, clipLabelString = NO;
854     
855     NSString *label = [element objectForKey: WebElementLinkLabelKey];
856     NSString *urlString = [linkURL _web_userVisibleString];
857     
858     if (!label) {
859         drawURLString = NO;
860         label = urlString;
861     }
862     
863     NSFont *labelFont = [[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:DRAG_LINK_LABEL_FONT_SIZE]
864                                                    toHaveTrait:NSBoldFontMask];
865     NSFont *urlFont = [NSFont systemFontOfSize: DRAG_LINK_URL_FONT_SIZE];
866     NSSize labelSize;
867     labelSize.width = [label _web_widthWithFont: labelFont];
868     labelSize.height = [labelFont ascender] - [labelFont descender];
869     if (labelSize.width > MAX_DRAG_LABEL_WIDTH){
870         labelSize.width = MAX_DRAG_LABEL_WIDTH;
871         clipLabelString = YES;
872     }
873     
874     NSSize imageSize, urlStringSize;
875     imageSize.width = labelSize.width + DRAG_LABEL_BORDER_X * 2;
876     imageSize.height = labelSize.height + DRAG_LABEL_BORDER_Y * 2;
877     if (drawURLString) {
878         urlStringSize.width = [urlString _web_widthWithFont: urlFont];
879         urlStringSize.height = [urlFont ascender] - [urlFont descender];
880         imageSize.height += urlStringSize.height;
881         if (urlStringSize.width > MAX_DRAG_LABEL_WIDTH) {
882             imageSize.width = MAX(MAX_DRAG_LABEL_WIDTH + DRAG_LABEL_BORDER_X * 2, MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP);
883             clipURLString = YES;
884         } else {
885             imageSize.width = MAX(labelSize.width + DRAG_LABEL_BORDER_X * 2, urlStringSize.width + DRAG_LABEL_BORDER_X * 2);
886         }
887     }
888     NSImage *dragImage = [[[NSImage alloc] initWithSize: imageSize] autorelease];
889     [dragImage lockFocus];
890     
891     [[NSColor colorWithCalibratedRed: 0.7 green: 0.7 blue: 0.7 alpha: 0.8] set];
892     
893     // Drag a rectangle with rounded corners/
894     NSBezierPath *path = [NSBezierPath bezierPath];
895     [path appendBezierPathWithOvalInRect: NSMakeRect(0,0, DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2)];
896     [path appendBezierPathWithOvalInRect: NSMakeRect(0,imageSize.height - DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2)];
897     [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2, imageSize.height - DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2)];
898     [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2,0, DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2)];
899     
900     [path appendBezierPathWithRect: NSMakeRect(DRAG_LABEL_RADIUS, 0, imageSize.width - DRAG_LABEL_RADIUS * 2, imageSize.height)];
901     [path appendBezierPathWithRect: NSMakeRect(0, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 10, imageSize.height - 2 * DRAG_LABEL_RADIUS)];
902     [path appendBezierPathWithRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS - 20,DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 20, imageSize.height - 2 * DRAG_LABEL_RADIUS)];
903     [path fill];
904         
905     NSColor *topColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.75];
906     NSColor *bottomColor = [NSColor colorWithCalibratedWhite:1.0 alpha:0.5];
907     if (drawURLString) {
908         if (clipURLString)
909             urlString = [WebStringTruncator centerTruncateString: urlString toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2) withFont:urlFont];
910
911         [urlString _web_drawDoubledAtPoint:NSMakePoint(DRAG_LABEL_BORDER_X, DRAG_LABEL_BORDER_Y - [urlFont descender]) 
912              withTopColor:topColor bottomColor:bottomColor font:urlFont];
913     }
914
915     if (clipLabelString)
916         label = [WebStringTruncator rightTruncateString: label toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2) withFont:labelFont];
917     [label _web_drawDoubledAtPoint:NSMakePoint (DRAG_LABEL_BORDER_X, imageSize.height - DRAG_LABEL_BORDER_Y_OFFSET - [labelFont pointSize])
918              withTopColor:topColor bottomColor:bottomColor font:labelFont];
919     
920     [dragImage unlockFocus];
921     
922     return dragImage;
923 }
924
925 - (BOOL)_startDraggingImage:(NSImage *)wcDragImage at:(NSPoint)wcDragLoc operation:(NSDragOperation)op event:(NSEvent *)mouseDraggedEvent sourceIsDHTML:(BOOL)srcIsDHTML DHTMLWroteData:(BOOL)dhtmlWroteData
926 {
927     NSPoint mouseDownPoint = [self convertPoint:[_private->mouseDownEvent locationInWindow] fromView:nil];
928     NSDictionary *element = [self elementAtPoint:mouseDownPoint];
929
930     NSURL *linkURL = [element objectForKey:WebElementLinkURLKey];
931     NSURL *imageURL = [element objectForKey:WebElementImageURLKey];
932     BOOL isSelected = [[element objectForKey:WebElementIsSelectedKey] boolValue];
933
934     [_private->draggingImageURL release];
935     _private->draggingImageURL = nil;
936
937     NSPoint mouseDraggedPoint = [self convertPoint:[mouseDraggedEvent locationInWindow] fromView:nil];
938     _private->webCoreDragOp = op;     // will be DragNone if WebCore doesn't care
939     NSImage *dragImage = nil;
940     NSPoint dragLoc;
941
942     // We allow WebCore to override the drag image, even if its a link, image or text we're dragging.
943     // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp.
944     // We could verify that ActionDHTML is allowed, although WebCore does claim to respect the action.
945     if (wcDragImage) {
946         dragImage = wcDragImage;
947         // wcDragLoc is the cursor position relative to the lower-left corner of the image.
948         // We add in the Y dimension because we are a flipped view, so adding moves the image down.
949         if (linkURL) {
950             // see HACK below
951             dragLoc = NSMakePoint(mouseDraggedPoint.x - wcDragLoc.x, mouseDraggedPoint.y + wcDragLoc.y);
952         } else {
953             dragLoc = NSMakePoint(mouseDownPoint.x - wcDragLoc.x, mouseDownPoint.y + wcDragLoc.y);
954         }
955         _private->dragOffset = wcDragLoc;
956     }
957     
958     WebView *webView = [self _webView];
959     NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
960     WebImageRenderer *image = [element objectForKey:WebElementImageKey];
961     BOOL startedDrag = YES;  // optimism - we almost always manage to start the drag
962
963     // note per kwebster, the offset arg below is always ignored in positioning the image
964     if (imageURL != nil && image != nil && (_private->dragSourceActionMask & WebDragSourceActionImage)) {
965         id source = self;
966         if (!dhtmlWroteData) {
967             // Select the image when it is dragged. This allows the image to be moved via MoveSelectionCommandImpl and this matches NSTextView's behavior.
968             DOMHTMLElement *imageElement = [element objectForKey:WebElementDOMNodeKey];
969             ASSERT(imageElement != nil);
970             [webView setSelectedDOMRange:[[[self _bridge] DOMDocument] _createRangeWithNode:imageElement] affinity:NSSelectionAffinityUpstream];
971             _private->draggingImageURL = [imageURL retain];
972             source = [pasteboard _web_declareAndWriteDragImage:image
973                                                            URL:linkURL ? linkURL : imageURL
974                                                          title:[element objectForKey:WebElementImageAltStringKey]
975                                                        archive:[imageElement webArchive]
976                                                         source:self];
977         }
978         [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionImage fromPoint:mouseDownPoint withPasteboard:pasteboard];
979         if (dragImage == nil) {
980             [self _web_dragImage:[element objectForKey:WebElementImageKey]
981                             rect:[[element objectForKey:WebElementImageRectKey] rectValue]
982                            event:_private->mouseDownEvent
983                       pasteboard:pasteboard
984                           source:source
985                           offset:&_private->dragOffset];
986         } else {
987             [self dragImage:dragImage
988                          at:dragLoc
989                      offset:NSZeroSize
990                       event:_private->mouseDownEvent
991                  pasteboard:pasteboard
992                      source:source
993                   slideBack:YES];
994         }
995     } else if (linkURL && (_private->dragSourceActionMask & WebDragSourceActionLink)) {
996         if (!dhtmlWroteData) {
997             NSArray *types = [NSPasteboard _web_writableTypesForURL];
998             [pasteboard declareTypes:types owner:self];
999             [pasteboard _web_writeURL:linkURL andTitle:[element objectForKey:WebElementLinkLabelKey] types:types];            
1000         }
1001         [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionLink fromPoint:mouseDownPoint withPasteboard:pasteboard];
1002         if (dragImage == nil) {
1003             dragImage = [self _dragImageForLinkElement:element];
1004             NSSize offset = NSMakeSize([dragImage size].width / 2, -DRAG_LABEL_BORDER_Y);
1005             dragLoc = NSMakePoint(mouseDraggedPoint.x - offset.width, mouseDraggedPoint.y - offset.height);
1006             _private->dragOffset.x = offset.width;
1007             _private->dragOffset.y = -offset.height;        // inverted because we are flipped
1008         }
1009         // HACK:  We should pass the mouseDown event instead of the mouseDragged!  This hack gets rid of
1010         // a flash of the image at the mouseDown location when the drag starts.
1011         [self dragImage:dragImage
1012                      at:dragLoc
1013                  offset:NSZeroSize
1014                   event:mouseDraggedEvent
1015              pasteboard:pasteboard
1016                  source:self
1017               slideBack:YES];
1018     } else if (isSelected && (_private->dragSourceActionMask & WebDragSourceActionSelection)) {
1019         if (!dhtmlWroteData) {
1020             [self _writeSelectionToPasteboard:pasteboard];
1021         }
1022         [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionSelection fromPoint:mouseDownPoint withPasteboard:pasteboard];
1023         if (dragImage == nil) {
1024             dragImage = [[self _bridge] selectionImage];
1025             [dragImage _web_dissolveToFraction:WebDragImageAlpha];
1026             NSRect visibleSelectionRect = [[self _bridge] visibleSelectionRect];
1027             dragLoc = NSMakePoint(NSMinX(visibleSelectionRect), NSMaxY(visibleSelectionRect));
1028             _private->dragOffset.x = mouseDownPoint.x - dragLoc.x;
1029             _private->dragOffset.y = dragLoc.y - mouseDownPoint.y;        // inverted because we are flipped
1030         }
1031         [self dragImage:dragImage
1032                      at:dragLoc
1033                  offset:NSZeroSize
1034                   event:_private->mouseDownEvent
1035              pasteboard:pasteboard
1036                  source:self
1037               slideBack:YES];
1038     } else if (srcIsDHTML) {
1039         ASSERT(_private->dragSourceActionMask & WebDragSourceActionDHTML);
1040         [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionDHTML fromPoint:mouseDownPoint withPasteboard:pasteboard];
1041         if (dragImage == nil) {
1042             // WebCore should have given us an image, but we'll make one up
1043             NSString *imagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_image" ofType:@"tiff"];
1044             dragImage = [[[NSImage alloc] initWithContentsOfFile:imagePath] autorelease];
1045             NSSize imageSize = [dragImage size];
1046             dragLoc = NSMakePoint(mouseDownPoint.x - imageSize.width/2, mouseDownPoint.y + imageSize.height/2);
1047             _private->dragOffset.x = imageSize.width/2;
1048             _private->dragOffset.y = imageSize.height/2;        // inverted because we are flipped
1049         }
1050         [self dragImage:dragImage
1051                      at:dragLoc
1052                  offset:NSZeroSize
1053                   event:_private->mouseDownEvent
1054              pasteboard:pasteboard
1055                  source:self
1056               slideBack:YES];
1057     } else {
1058         // Only way I know if to get here is if the original element clicked on in the mousedown is no longer
1059         // under the mousedown point, so linkURL, imageURL and isSelected are all false/nil.
1060         startedDrag = NO;
1061     }
1062     return startedDrag;
1063 }
1064
1065 - (void)_handleAutoscrollForMouseDragged:(NSEvent *)event
1066 {
1067     [self autoscroll:event];
1068     [self _startAutoscrollTimer:event];
1069 }
1070
1071 - (BOOL)_mayStartDragAtEventLocation:(NSPoint)location
1072 {
1073     NSPoint mouseDownPoint = [self convertPoint:location fromView:nil];
1074     NSDictionary *mouseDownElement = [self elementAtPoint:mouseDownPoint];
1075
1076     if ([mouseDownElement objectForKey: WebElementImageKey] != nil &&
1077         [mouseDownElement objectForKey: WebElementImageURLKey] != nil && 
1078         [[WebPreferences standardPreferences] loadsImagesAutomatically] && 
1079         (_private->dragSourceActionMask & WebDragSourceActionImage)) {
1080         return YES;
1081     }
1082     
1083     if ([mouseDownElement objectForKey:WebElementLinkURLKey] != nil && 
1084         (_private->dragSourceActionMask & WebDragSourceActionLink)) {
1085         return YES;
1086     }
1087     
1088     if ([[mouseDownElement objectForKey:WebElementIsSelectedKey] boolValue] &&
1089         (_private->dragSourceActionMask & WebDragSourceActionSelection)) {
1090         return YES;
1091     }
1092     
1093     return NO;
1094 }
1095
1096 - (WebPluginController *)_pluginController
1097 {
1098     return _private->pluginController;
1099 }
1100
1101 - (void)_web_setPrintingModeRecursive
1102 {
1103     [self _setPrinting:YES minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:NO];
1104     [super _web_setPrintingModeRecursive];
1105 }
1106
1107 - (void)_web_clearPrintingModeRecursive
1108 {
1109     [self _setPrinting:NO minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:NO];
1110     [super _web_clearPrintingModeRecursive];
1111 }
1112
1113 - (void)_web_layoutIfNeededRecursive:(NSRect)displayRect testDirtyRect:(bool)testDirtyRect
1114 {
1115     ASSERT(!_private->subviewsSetAside);
1116     displayRect = NSIntersectionRect(displayRect, [self bounds]);
1117
1118     if (!testDirtyRect || [self needsDisplay]) {
1119         if (testDirtyRect) {
1120             NSRect dirtyRect = [self _dirtyRect];
1121             displayRect = NSIntersectionRect(displayRect, dirtyRect);
1122         }
1123         if (!NSIsEmptyRect(displayRect)) {
1124             if ([[self _bridge] needsLayout])
1125                 _private->needsLayout = YES;
1126             if (_private->needsToApplyStyles || _private->needsLayout)
1127                 [self layout];
1128         }
1129     }
1130
1131     [super _web_layoutIfNeededRecursive: displayRect testDirtyRect: NO];
1132 }
1133
1134 - (NSRect)_selectionRect
1135 {
1136     return [[self _bridge] selectionRect];
1137 }
1138
1139 - (void)_startAutoscrollTimer: (NSEvent *)triggerEvent
1140 {
1141     if (_private->autoscrollTimer == nil) {
1142         _private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL
1143             target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES] retain];
1144         _private->autoscrollTriggerEvent = [triggerEvent retain];
1145     }
1146 }
1147
1148 - (void)_stopAutoscrollTimer
1149 {
1150     NSTimer *timer = _private->autoscrollTimer;
1151     _private->autoscrollTimer = nil;
1152     [_private->autoscrollTriggerEvent release];
1153     _private->autoscrollTriggerEvent = nil;
1154     [timer invalidate];
1155     [timer release];
1156 }
1157
1158 - (void)_autoscroll
1159 {
1160     int isStillDown;
1161     
1162     // Guarantee that the autoscroll timer is invalidated, even if we don't receive
1163     // a mouse up event.
1164     PSstilldown([_private->autoscrollTriggerEvent eventNumber], &isStillDown);
1165     if (!isStillDown){
1166         [self _stopAutoscrollTimer];
1167         return;
1168     }
1169
1170     NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
1171         location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
1172         modifierFlags:[[NSApp currentEvent] modifierFlags]
1173         timestamp:[NSDate timeIntervalSinceReferenceDate]
1174         windowNumber:[[self window] windowNumber]
1175         context:[[NSApp currentEvent] context]
1176         eventNumber:0 clickCount:0 pressure:0];
1177     [self mouseDragged:fakeEvent];
1178 }
1179
1180 - (BOOL)_canCopy
1181 {
1182     // Copying can be done regardless of whether you can edit.
1183     return [self _hasSelection];
1184 }
1185
1186 - (BOOL)_canCut
1187 {
1188     return [self _hasSelection] && [self _isEditable];
1189 }
1190
1191 - (BOOL)_canDelete
1192 {
1193     return [self _hasSelection] && [self _isEditable];
1194 }
1195
1196 - (BOOL)_canPaste
1197 {
1198     return [self _hasSelectionOrInsertionPoint] && [self _isEditable];
1199 }
1200
1201 - (BOOL)_canEdit
1202 {
1203     return [self _hasSelectionOrInsertionPoint] && [self _isEditable];
1204 }
1205
1206 - (BOOL)_hasSelection
1207 {
1208     return [[self _bridge] selectionState] == WebSelectionStateRange;
1209 }
1210
1211 - (BOOL)_hasSelectionOrInsertionPoint
1212 {
1213     return [[self _bridge] selectionState] != WebSelectionStateNone;
1214 }
1215
1216 - (BOOL)_isEditable
1217 {
1218     return [[self _webView] isEditable] || [[self _bridge] isSelectionEditable];
1219 }
1220
1221 - (BOOL)_isSelectionMisspelled
1222 {
1223     NSString *selectedString = [self selectedString];
1224     unsigned length = [selectedString length];
1225     if (length == 0) {
1226         return NO;
1227     }
1228     NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:selectedString
1229                                                                     startingAt:0
1230                                                                       language:@""
1231                                                                           wrap:NO
1232                                                         inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]
1233                                                                      wordCount:NULL];
1234     return range.length == length;
1235 }
1236
1237 - (NSArray *)_guessesForMisspelledSelection
1238 {
1239     ASSERT([[self selectedString] length] != 0);
1240     return [[NSSpellChecker sharedSpellChecker] guessesForWord:[self selectedString]];
1241 }
1242
1243 - (void)_changeSpellingFromMenu:(id)sender
1244 {
1245     ASSERT([[self selectedString] length] != 0);
1246     [[self _bridge] replaceSelectionWithText:[sender title] selectReplacement:YES smartReplace:NO];
1247 }
1248
1249 - (void)_ignoreSpellingFromMenu:(id)sender
1250 {
1251     ASSERT([[self selectedString] length] != 0);
1252     [[NSSpellChecker sharedSpellChecker] ignoreWord:[self selectedString] inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]];
1253 }
1254
1255 - (void)_learnSpellingFromMenu:(id)sender
1256 {
1257     ASSERT([[self selectedString] length] != 0);
1258     [[NSSpellChecker sharedSpellChecker] learnWord:[self selectedString]];
1259 }
1260
1261 #if APPKIT_CODE_FOR_REFERENCE
1262
1263 - (void)_openLinkFromMenu:(id)sender
1264 {
1265     NSTextStorage *text = _getTextStorage(self);
1266     NSRange charRange = [self selectedRange];
1267     if (charRange.location != NSNotFound && charRange.length > 0) {
1268         id link = [text attribute:NSLinkAttributeName atIndex:charRange.location effectiveRange:NULL];
1269         if (link) {
1270             [self clickedOnLink:link atIndex:charRange.location];
1271         } else {
1272             NSString *string = [[text string] substringWithRange:charRange];
1273             link = [NSURL URLWithString:string];
1274             if (link) [[NSWorkspace sharedWorkspace] openURL:link];
1275         }
1276     }
1277 }
1278
1279 #endif
1280
1281 @end
1282
1283 @implementation NSView (WebHTMLViewFileInternal)
1284
1285 - (void)_web_setPrintingModeRecursive
1286 {
1287     [_subviews makeObjectsPerformSelector:@selector(_web_setPrintingModeRecursive)];
1288 }
1289
1290 - (void)_web_clearPrintingModeRecursive
1291 {
1292     [_subviews makeObjectsPerformSelector:@selector(_web_clearPrintingModeRecursive)];
1293 }
1294
1295 - (void)_web_layoutIfNeededRecursive: (NSRect)rect testDirtyRect:(bool)testDirtyRect
1296 {
1297     unsigned index, count;
1298     for (index = 0, count = [_subviews count]; index < count; index++) {
1299         NSView *subview = [_subviews objectAtIndex:index];
1300         NSRect dirtiedSubviewRect = [subview convertRect: rect fromView: self];
1301         [subview _web_layoutIfNeededRecursive: dirtiedSubviewRect testDirtyRect:testDirtyRect];
1302     }
1303 }
1304
1305 @end
1306
1307 @implementation NSMutableDictionary (WebHTMLViewFileInternal)
1308
1309 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key
1310 {
1311     if (object == nil) {
1312         [self removeObjectForKey:key];
1313     } else {
1314         [self setObject:object forKey:key];
1315     }
1316 }
1317
1318 @end
1319
1320 // The following is a workaround for
1321 // <rdar://problem/3429631> window stops getting mouse moved events after first tooltip appears
1322 // The trick is to define a category on NSToolTipPanel that implements setAcceptsMouseMovedEvents:.
1323 // Since the category will be searched before the real class, we'll prevent the flag from being
1324 // set on the tool tip panel.
1325
1326 @interface NSToolTipPanel : NSPanel
1327 @end
1328
1329 @interface NSToolTipPanel (WebHTMLViewFileInternal)
1330 @end
1331
1332 @implementation NSToolTipPanel (WebHTMLViewFileInternal)
1333
1334 - (void)setAcceptsMouseMovedEvents:(BOOL)flag
1335 {
1336     // Do nothing, preventing the tool tip panel from trying to accept mouse-moved events.
1337 }
1338
1339 @end
1340
1341
1342 @interface WebHTMLView (TextSizing) <_web_WebDocumentTextSizing>
1343 @end
1344
1345 @interface NSArray (WebHTMLView)
1346 - (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object;
1347 @end
1348
1349 @implementation WebHTMLView
1350
1351 + (void)initialize
1352 {
1353     WebKitInitializeUnicode();
1354     [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes] returnTypes:nil];
1355     _NSInitializeKillRing();
1356 }
1357
1358 - (id)initWithFrame:(NSRect)frame
1359 {
1360     [super initWithFrame:frame];
1361     
1362     // Make all drawing go through us instead of subviews.
1363     if (NSAppKitVersionNumber >= 711) {
1364         [self _setDrawsOwnDescendants:YES];
1365     }
1366     
1367     _private = [[WebHTMLViewPrivate alloc] init];
1368
1369     _private->pluginController = [[WebPluginController alloc] initWithHTMLView:self];
1370     _private->needsLayout = YES;
1371
1372     return self;
1373 }
1374
1375 - (void)dealloc
1376 {
1377     [self _clearLastHitViewIfSelf];
1378     [self _reset];
1379     [[NSNotificationCenter defaultCenter] removeObserver:self];
1380     [_private->pluginController destroyAllPlugins];
1381     [_private release];
1382     _private = nil;
1383     [super dealloc];
1384 }
1385
1386 - (void)finalize
1387 {
1388     [self _clearLastHitViewIfSelf];
1389     [self _reset];
1390     [[NSNotificationCenter defaultCenter] removeObserver:self];
1391     [_private->pluginController destroyAllPlugins];
1392     _private = nil;
1393     [super finalize];
1394 }
1395
1396 - (IBAction)takeFindStringFromSelection:(id)sender
1397 {
1398     if (![self _hasSelection]) {
1399         NSBeep();
1400         return;
1401     }
1402
1403     [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self];
1404 }
1405
1406 - (NSArray *)pasteboardTypesForSelection
1407 {
1408     if ([self _canSmartCopyOrDelete]) {
1409         NSMutableArray *types = [[[[self class] _selectionPasteboardTypes] mutableCopy] autorelease];
1410         [types addObject:WebSmartPastePboardType];
1411         return types;
1412     } else {
1413         return [[self class] _selectionPasteboardTypes];
1414     }
1415 }
1416
1417 // This method is copied from NSTextView
1418 - (NSAttributedString *)_stripAttachmentCharactersFromAttributedString:(NSAttributedString *)originalAttributedString
1419 {
1420     NSRange attachmentRange;
1421     NSString *originalString = [originalAttributedString string];
1422     static NSString *attachmentCharString = nil;
1423     
1424     if (!attachmentCharString) {
1425         unichar chars[2];
1426         if (!attachmentCharString) {
1427             chars[0] = NSAttachmentCharacter;
1428             chars[1] = 0;
1429             attachmentCharString = [[NSString alloc] initWithCharacters:chars length:1];
1430         }
1431     }
1432     
1433     attachmentRange = [originalString rangeOfString:attachmentCharString];
1434     if (attachmentRange.location != NSNotFound && attachmentRange.length > 0) {
1435         NSMutableAttributedString *newAttributedString = [[originalAttributedString mutableCopyWithZone:NULL] autorelease];
1436         
1437         while (attachmentRange.location != NSNotFound && attachmentRange.length > 0) {
1438             [newAttributedString replaceCharactersInRange:attachmentRange withString:@""];
1439             attachmentRange = [[newAttributedString string] rangeOfString:attachmentCharString];
1440         }
1441         return newAttributedString;
1442     } else {
1443         return originalAttributedString;
1444     }
1445 }
1446
1447 - (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard
1448 {
1449     // Put HTML on the pasteboard.
1450     if ([types containsObject:WebArchivePboardType]) {
1451         WebArchive *archive = [self _selectedArchive];
1452         [pasteboard setData:[archive data] forType:WebArchivePboardType];
1453     }
1454     
1455     // Put the attributed string on the pasteboard (RTF/RTFD format).
1456     NSAttributedString *attributedString = nil;
1457     if ([types containsObject:NSRTFDPboardType]) {
1458         attributedString = [self selectedAttributedString];
1459         if ([attributedString containsAttachments]) {
1460             NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
1461             [pasteboard setData:RTFDData forType:NSRTFDPboardType];
1462         }
1463     }        
1464     if ([types containsObject:NSRTFPboardType]) {
1465         if (attributedString == nil) {
1466             attributedString = [self selectedAttributedString];
1467         }
1468         if ([attributedString containsAttachments]) {
1469             attributedString = [self _stripAttachmentCharactersFromAttributedString:attributedString];
1470         }
1471         NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
1472         [pasteboard setData:RTFData forType:NSRTFPboardType];
1473     }
1474         
1475     
1476     // Put plain string on the pasteboard.
1477     if ([types containsObject:NSStringPboardType]) {
1478         // Map &nbsp; to a plain old space because this is better for source code, other browsers do it,
1479         // and because HTML forces you to do this any time you want two spaces in a row.
1480         NSMutableString *s = [[self selectedString] mutableCopy];
1481         const unichar NonBreakingSpaceCharacter = 0xA0;
1482         NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&NonBreakingSpaceCharacter length:1];
1483         [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])];
1484         [pasteboard setString:s forType:NSStringPboardType];
1485         [s release];
1486     }
1487     
1488     if ([self _canSmartCopyOrDelete] && [types containsObject:WebSmartPastePboardType]) {
1489         [pasteboard setData:nil forType:WebSmartPastePboardType];
1490     }
1491 }
1492
1493 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types
1494 {
1495     [self _writeSelectionToPasteboard:pasteboard];
1496     return YES;
1497 }
1498
1499 - (void)selectAll:(id)sender
1500 {
1501     [self selectAll];
1502 }
1503
1504 - (void)jumpToSelection: sender
1505 {
1506     [[self _bridge] jumpToSelection];
1507 }
1508
1509 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item 
1510 {
1511     SEL action = [item action];
1512     WebBridge *bridge = [self _bridge];
1513
1514     if (action == @selector(cut:)) {
1515         return [bridge mayDHTMLCut] || [self _canDelete];
1516     } else if (action == @selector(copy:)) {
1517         return [bridge mayDHTMLCopy] || [self _canCopy];
1518     } else if (action == @selector(delete:)) {
1519         return [self _canDelete];
1520     } else if (action == @selector(paste:)) {
1521         return [bridge mayDHTMLPaste] || [self _canPaste];
1522     } else if (action == @selector(takeFindStringFromSelection:)) {
1523         return [self _hasSelection];
1524     } else if (action == @selector(jumpToSelection:)) {
1525         return [self _hasSelection];
1526     } else if (action == @selector(checkSpelling:)
1527                || action == @selector(showGuessPanel:)
1528                || action == @selector(changeSpelling:)
1529                || action == @selector(ignoreSpelling:)) {
1530         return [[self _bridge] isSelectionEditable];
1531     }
1532
1533     return YES;
1534 }
1535
1536 - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
1537 {
1538     if (sendType && ([[[self class] _selectionPasteboardTypes] containsObject:sendType]) && [self _hasSelection]){
1539         return self;
1540     }
1541
1542     return [super validRequestorForSendType:sendType returnType:returnType];
1543 }
1544
1545 - (BOOL)acceptsFirstResponder
1546 {
1547     // Don't accept first responder when we first click on this view.
1548     // We have to pass the event down through WebCore first to be sure we don't hit a subview.
1549     // Do accept first responder at any other time, for example from keyboard events,
1550     // or from calls back from WebCore once we begin mouse-down event handling.
1551     NSEvent *event = [NSApp currentEvent];
1552     if ([event type] == NSLeftMouseDown && event != _private->mouseDownEvent && 
1553         NSPointInRect([event locationInWindow], [self convertRect:[self visibleRect] toView:nil])) {
1554         return NO;
1555     }
1556     return YES;
1557 }
1558
1559 - (BOOL)maintainsInactiveSelection
1560 {
1561     // This method helps to determing whether the view should maintain
1562     // an inactive selection when the view is not first responder.
1563     // Traditionally, these views have not maintained such selections,
1564     // clearing them when the view was not first responder. However,
1565     // to fix bugs like this one:
1566     // <rdar://problem/3672088>: "Editable WebViews should maintain a selection even 
1567     //                            when they're not firstResponder"
1568     // it was decided to add a switch to act more like an NSTextView.
1569     // For now, however, the view only acts in this way when the
1570     // web view is set to be editable. This will maintain traditional
1571     // behavior for WebKit clients dating back to before this change,
1572     // and will likely be a decent switch for the long term, since
1573     // clients to ste the web view to be editable probably want it
1574     // to act like a "regular" Cocoa view in terms of its selection
1575     // behavior.
1576     if (![[self _webView] isEditable])
1577         return NO;
1578         
1579     id nextResponder = [[self window] _newFirstResponderAfterResigning];
1580     return !nextResponder || ![nextResponder isKindOfClass:[NSView class]] || ![nextResponder isDescendantOf:[self _webView]];
1581 }
1582
1583 - (void)addMouseMovedObserver
1584 {
1585     if ([[self window] isKeyWindow] && ![self _insideAnotherHTMLView]) {
1586         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mouseMovedNotification:)
1587             name:NSMouseMovedNotification object:nil];
1588         [self _frameOrBoundsChanged];
1589     }
1590 }
1591
1592 - (void)removeMouseMovedObserver
1593 {
1594     [[self _webView] _mouseDidMoveOverElement:nil modifierFlags:0];
1595     [[NSNotificationCenter defaultCenter] removeObserver:self
1596         name:NSMouseMovedNotification object:nil];
1597 }
1598
1599 - (void)updateFocusDisplay
1600 {
1601     // This method does the job of updating the view based on the view's firstResponder-ness and
1602     // the window key-ness of the window containing this view. This involves three kinds of 
1603     // drawing updates right now, all handled in WebCore in response to the call over the bridge. 
1604     // 
1605     // The three display attributes are as follows:
1606     // 
1607     // 1. The background color used to draw behind selected content (active | inactive color)
1608     // 2. Caret blinking (blinks | does not blink)
1609     // 3. The drawing of a focus ring around links in web pages.
1610
1611     BOOL flag = !_private->resigningFirstResponder && [[self window] isKeyWindow] && [self _web_firstResponderCausesFocusDisplay];
1612     [[self _bridge] setDisplaysWithFocusAttributes:flag];
1613 }
1614
1615 - (void)addSuperviewObservers
1616 {
1617     // We watch the bounds of our superview, so that we can do a layout when the size
1618     // of the superview changes. This is different from other scrollable things that don't
1619     // need this kind of thing because their layout doesn't change.
1620     
1621     // We need to pay attention to both height and width because our "layout" has to change
1622     // to extend the background the full height of the space and because some elements have
1623     // sizes that are based on the total size of the view.
1624     
1625     NSView *superview = [self superview];
1626     if (superview && [self window]) {
1627         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) 
1628             name:NSViewFrameDidChangeNotification object:superview];
1629         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) 
1630             name:NSViewBoundsDidChangeNotification object:superview];
1631     }
1632 }
1633
1634 - (void)removeSuperviewObservers
1635 {
1636     NSView *superview = [self superview];
1637     if (superview && [self window]) {
1638         [[NSNotificationCenter defaultCenter] removeObserver:self
1639             name:NSViewFrameDidChangeNotification object:superview];
1640         [[NSNotificationCenter defaultCenter] removeObserver:self
1641             name:NSViewBoundsDidChangeNotification object:superview];
1642     }
1643 }
1644
1645 - (void)addWindowObservers
1646 {
1647     NSWindow *window = [self window];
1648     if (window) {
1649         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeKey:)
1650             name:NSWindowDidBecomeKeyNotification object:window];
1651         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResignKey:)
1652             name:NSWindowDidResignKeyNotification object:window];
1653         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:)
1654             name:NSWindowWillCloseNotification object:window];
1655     }
1656 }
1657
1658 - (void)removeWindowObservers
1659 {
1660     NSWindow *window = [self window];
1661     if (window) {
1662         [[NSNotificationCenter defaultCenter] removeObserver:self
1663             name:NSWindowDidBecomeKeyNotification object:window];
1664         [[NSNotificationCenter defaultCenter] removeObserver:self
1665             name:NSWindowDidResignKeyNotification object:window];
1666         [[NSNotificationCenter defaultCenter] removeObserver:self
1667             name:NSWindowWillCloseNotification object:window];
1668     }
1669 }
1670
1671 - (void)viewWillMoveToSuperview:(NSView *)newSuperview
1672 {
1673     [self removeSuperviewObservers];
1674 }
1675
1676 - (void)viewDidMoveToSuperview
1677 {
1678     // Do this here in case the text size multiplier changed when a non-HTML
1679     // view was installed.
1680     if ([self superview] != nil) {
1681         [self _updateTextSizeMultiplier];
1682         [self addSuperviewObservers];
1683     }
1684 }
1685
1686 - (void)viewWillMoveToWindow:(NSWindow *)window
1687 {
1688     // Don't do anything if we aren't initialized.  This happens
1689     // when decoding a WebView.  When WebViews are decoded their subviews
1690     // are created by initWithCoder: and so won't be normally
1691     // initialized.  The stub views are discarded by WebView.
1692     if (_private){
1693         // FIXME: Some of these calls may not work because this view may be already removed from it's superview.
1694         [self removeMouseMovedObserver];
1695         [self removeWindowObservers];
1696         [self removeSuperviewObservers];
1697         [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_updateMouseoverWithFakeEvent) object:nil];
1698     
1699         [[self _pluginController] stopAllPlugins];
1700     }
1701 }
1702
1703 - (void)viewDidMoveToWindow
1704 {
1705     // Don't do anything if we aren't initialized.  This happens
1706     // when decoding a WebView.  When WebViews are decoded their subviews
1707     // are created by initWithCoder: and so won't be normally
1708     // initialized.  The stub views are discarded by WebView.
1709     if (_private) {
1710         [self _stopAutoscrollTimer];
1711         if ([self window]) {
1712             _private->lastScrollPosition = [[self superview] bounds].origin;
1713             [self addWindowObservers];
1714             [self addSuperviewObservers];
1715             [self addMouseMovedObserver];
1716             // Schedule this update, rather than making the call right now.
1717             // The reason is that placing the caret in the just-installed view requires
1718             // the HTML/XML document to be available on the WebCore side, but it is not
1719             // at the time this code is running. However, it will be there on the next
1720             // crank of the run loop. Doing this helps to make a blinking caret appear 
1721             // in a new, empty window "automatic".
1722             [self performSelector:@selector(updateFocusDisplay) withObject:nil afterDelay:0];
1723     
1724             [[self _pluginController] startAllPlugins];
1725     
1726             _private->lastScrollPosition = NSZeroPoint;
1727             
1728             _private->inWindow = YES;
1729         } else {
1730             // Reset when we are moved out of a window after being moved into one.
1731             // Without this check, we reset ourselves before we even start.
1732             // This is only needed because viewDidMoveToWindow is called even when
1733             // the window is not changing (bug in AppKit).
1734             if (_private->inWindow) {
1735                 [self _reset];
1736                 _private->inWindow = NO;
1737             }
1738         }
1739     }
1740 }
1741
1742 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
1743 {
1744     [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewWillMoveToHostWindow:) withObject:hostWindow];
1745 }
1746
1747 - (void)viewDidMoveToHostWindow
1748 {
1749     [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewDidMoveToHostWindow) withObject:nil];
1750 }
1751
1752
1753 - (void)addSubview:(NSView *)view
1754 {
1755     [super addSubview:view];
1756
1757     if ([[view class] respondsToSelector:@selector(plugInViewWithArguments:)] || [view respondsToSelector:@selector(pluginInitialize)] || [view respondsToSelector:@selector(webPlugInInitialize)]) {
1758         [[self _pluginController] addPlugin:view];
1759     }
1760 }
1761
1762 - (void)reapplyStyles
1763 {
1764     if (!_private->needsToApplyStyles) {
1765         return;
1766     }
1767     
1768 #ifdef _KWQ_TIMING        
1769     double start = CFAbsoluteTimeGetCurrent();
1770 #endif
1771
1772     [[self _bridge] reapplyStylesForDeviceType:
1773         _private->printing ? WebCoreDevicePrinter : WebCoreDeviceScreen];
1774     
1775 #ifdef _KWQ_TIMING        
1776     double thisTime = CFAbsoluteTimeGetCurrent() - start;
1777     LOG(Timing, "%s apply style seconds = %f", [self URL], thisTime);
1778 #endif
1779
1780     _private->needsToApplyStyles = NO;
1781 }
1782
1783 // Do a layout, but set up a new fixed width for the purposes of doing printing layout.
1784 // minPageWidth==0 implies a non-printing layout
1785 - (void)layoutToMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)adjustViewSize
1786 {
1787     [self reapplyStyles];
1788     
1789     // Ensure that we will receive mouse move events.  Is this the best place to put this?
1790     [[self window] setAcceptsMouseMovedEvents: YES];
1791     [[self window] _setShouldPostEventNotifications: YES];
1792
1793     if (!_private->needsLayout) {
1794         return;
1795     }
1796
1797 #ifdef _KWQ_TIMING        
1798     double start = CFAbsoluteTimeGetCurrent();
1799 #endif
1800
1801     LOG(View, "%@ doing layout", self);
1802
1803     if (minPageWidth > 0.0) {
1804         [[self _bridge] forceLayoutWithMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize];
1805     } else {
1806         [[self _bridge] forceLayoutAdjustingViewSize:adjustViewSize];
1807     }
1808     _private->needsLayout = NO;
1809     
1810     if (!_private->printing) {
1811         // get size of the containing dynamic scrollview, so
1812         // appearance and disappearance of scrollbars will not show up
1813         // as a size change
1814         NSSize newLayoutFrameSize = [[[self superview] superview] frame].size;
1815         if (_private->laidOutAtLeastOnce && !NSEqualSizes(_private->lastLayoutFrameSize, newLayoutFrameSize)) {
1816             [[self _bridge] sendResizeEvent];
1817         }
1818         _private->laidOutAtLeastOnce = YES;
1819         _private->lastLayoutSize = [(NSClipView *)[self superview] documentVisibleRect].size;
1820         _private->lastLayoutFrameSize = newLayoutFrameSize;
1821     }
1822
1823 #ifdef _KWQ_TIMING        
1824     double thisTime = CFAbsoluteTimeGetCurrent() - start;
1825     LOG(Timing, "%s layout seconds = %f", [self URL], thisTime);
1826 #endif
1827 }
1828
1829 - (void)layout
1830 {
1831     [self layoutToMinimumPageWidth:0.0 maximumPageWidth:0.0 adjustingViewSize:NO];
1832 }
1833
1834 - (NSMenu *)menuForEvent:(NSEvent *)event
1835 {
1836     [_private->compController endRevertingChange:NO moveLeft:NO];
1837
1838     if ([[self _bridge] sendContextMenuEvent:event]) {
1839         return nil;
1840     }
1841     NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
1842     NSDictionary *element = [self elementAtPoint:point];
1843     return [[self _webView] _menuForElement:element];
1844 }
1845
1846 // Search from the end of the currently selected location, or from the beginning of the
1847 // document if nothing is selected.
1848 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag;
1849 {
1850     return [[self _bridge] searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag];
1851 }
1852
1853 - (DOMRange *)_documentRange
1854 {
1855     return [[[self _bridge] DOMDocument] _documentRange];
1856 }
1857
1858 - (NSString *)string
1859 {
1860     return [[self _bridge] stringForRange:[self _documentRange]];
1861 }
1862
1863 - (NSAttributedString *)_attributeStringFromDOMRange:(DOMRange *)range
1864 {
1865     NSAttributedString *attributedString = nil;
1866 #ifdef USE_APPKIT_FOR_ATTRIBUTED_STRINGS
1867     attributedString = [[[NSAttributedString alloc] _initWithDOMRange:range] autorelease];
1868 #endif
1869     return attributedString;
1870 }
1871
1872 - (NSAttributedString *)attributedString
1873 {
1874     WebBridge *bridge = [self _bridge];
1875     DOMDocument *document = [bridge DOMDocument];
1876     NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[document _documentRange]];
1877     if (attributedString == nil) {
1878         attributedString = [bridge attributedStringFrom:document startOffset:0 to:nil endOffset:0];
1879     }
1880     return attributedString;
1881 }
1882
1883 - (NSString *)selectedString
1884 {
1885     return [[self _bridge] selectedString];
1886 }
1887
1888 - (NSAttributedString *)selectedAttributedString
1889 {
1890     WebBridge *bridge = [self _bridge];
1891     NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[self _selectedRange]];
1892     if (attributedString == nil) {
1893         attributedString = [bridge selectedAttributedString];
1894     }
1895     return attributedString;
1896 }
1897
1898 - (void)selectAll
1899 {
1900     [[self _bridge] selectAll];
1901 }
1902
1903 // Remove the selection.
1904 - (void)deselectAll
1905 {
1906     [[self _bridge] deselectAll];
1907 }
1908
1909 - (void)deselectText
1910 {
1911     [[self _bridge] deselectText];
1912 }
1913
1914 - (BOOL)isOpaque
1915 {
1916     return [[self _webView] drawsBackground];
1917 }
1918
1919 - (void)setNeedsDisplay:(BOOL)flag
1920 {
1921     LOG(View, "%@ flag = %d", self, (int)flag);
1922     [super setNeedsDisplay: flag];
1923 }
1924
1925 - (void)setNeedsLayout: (BOOL)flag
1926 {
1927     LOG(View, "%@ flag = %d", self, (int)flag);
1928     _private->needsLayout = flag;
1929 }
1930
1931
1932 - (void)setNeedsToApplyStyles: (BOOL)flag
1933 {
1934     LOG(View, "%@ flag = %d", self, (int)flag);
1935     _private->needsToApplyStyles = flag;
1936 }
1937
1938 - (void)drawRect:(NSRect)rect
1939 {
1940     LOG(View, "%@ drawing", self);
1941     
1942     BOOL subviewsWereSetAside = _private->subviewsSetAside;
1943     if (subviewsWereSetAside) {
1944         [self _restoreSubviews];
1945     }
1946
1947 #ifdef _KWQ_TIMING
1948     double start = CFAbsoluteTimeGetCurrent();
1949 #endif
1950
1951     [NSGraphicsContext saveGraphicsState];
1952     NSRectClip(rect);
1953         
1954     ASSERT([[self superview] isKindOfClass:[WebClipView class]]);
1955
1956     [(WebClipView *)[self superview] setAdditionalClip:rect];
1957
1958     NS_DURING {
1959         WebTextRendererFactory *textRendererFactoryIfCoalescing = nil;
1960         if ([WebTextRenderer shouldBufferTextDrawing] && [NSView focusView]) {
1961             textRendererFactoryIfCoalescing = [WebTextRendererFactory sharedFactory];
1962             [textRendererFactoryIfCoalescing startCoalesceTextDrawing];
1963         }
1964
1965         if (![[self _webView] drawsBackground]) {
1966             [[NSColor clearColor] set];
1967             NSRectFill (rect);
1968         }
1969         
1970         //double start = CFAbsoluteTimeGetCurrent();
1971         [[self _bridge] drawRect:rect];
1972         //LOG(Timing, "draw time %e", CFAbsoluteTimeGetCurrent() - start);
1973
1974         if (textRendererFactoryIfCoalescing != nil) {
1975             [textRendererFactoryIfCoalescing endCoalesceTextDrawing];
1976         }
1977
1978         [(WebClipView *)[self superview] resetAdditionalClip];
1979
1980         [NSGraphicsContext restoreGraphicsState];
1981     } NS_HANDLER {
1982         [(WebClipView *)[self superview] resetAdditionalClip];
1983         [NSGraphicsContext restoreGraphicsState];
1984         ERROR("Exception caught while drawing: %@", localException);
1985         [localException raise];
1986     } NS_ENDHANDLER
1987
1988 #ifdef DEBUG_LAYOUT
1989     NSRect vframe = [self frame];
1990     [[NSColor blackColor] set];
1991     NSBezierPath *path;
1992     path = [NSBezierPath bezierPath];
1993     [path setLineWidth:(float)0.1];
1994     [path moveToPoint:NSMakePoint(0, 0)];
1995     [path lineToPoint:NSMakePoint(vframe.size.width, vframe.size.height)];
1996     [path closePath];
1997     [path stroke];
1998     path = [NSBezierPath bezierPath];
1999     [path setLineWidth:(float)0.1];
2000     [path moveToPoint:NSMakePoint(0, vframe.size.height)];
2001     [path lineToPoint:NSMakePoint(vframe.size.width, 0)];
2002     [path closePath];
2003     [path stroke];
2004 #endif
2005
2006 #ifdef _KWQ_TIMING
2007     double thisTime = CFAbsoluteTimeGetCurrent() - start;
2008     LOG(Timing, "%s draw seconds = %f", widget->part()->baseURL().URL().latin1(), thisTime);
2009 #endif
2010
2011     if (subviewsWereSetAside) {
2012         [self _setAsideSubviews];
2013     }
2014 }
2015
2016 // Turn off the additional clip while computing our visibleRect.
2017 - (NSRect)visibleRect
2018 {
2019     if (!([[self superview] isKindOfClass:[WebClipView class]]))
2020         return [super visibleRect];
2021         
2022     WebClipView *clipView = (WebClipView *)[self superview];
2023
2024     BOOL hasAdditionalClip = [clipView hasAdditionalClip];
2025     if (!hasAdditionalClip) {
2026         return [super visibleRect];
2027     }
2028     
2029     NSRect additionalClip = [clipView additionalClip];
2030     [clipView resetAdditionalClip];
2031     NSRect visibleRect = [super visibleRect];
2032     [clipView setAdditionalClip:additionalClip];
2033     return visibleRect;
2034 }
2035
2036 - (BOOL)isFlipped 
2037 {
2038     return YES;
2039 }
2040
2041 - (void)windowDidBecomeKey:(NSNotification *)notification
2042 {
2043     ASSERT([notification object] == [self window]);
2044     [self addMouseMovedObserver];
2045     [self updateFocusDisplay];
2046 }
2047
2048 - (void)windowDidResignKey: (NSNotification *)notification
2049 {
2050     ASSERT([notification object] == [self window]);
2051     [_private->compController endRevertingChange:NO moveLeft:NO];
2052     [self removeMouseMovedObserver];
2053     [self updateFocusDisplay];
2054 }
2055
2056 - (void)windowWillClose:(NSNotification *)notification
2057 {
2058     [_private->compController endRevertingChange:NO moveLeft:NO];
2059     [[self _pluginController] destroyAllPlugins];
2060 }
2061
2062 - (BOOL)_isSelectionEvent:(NSEvent *)event
2063 {
2064     NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
2065     return [[[self elementAtPoint:point] objectForKey:WebElementIsSelectedKey] boolValue];
2066 }
2067
2068 - (BOOL)acceptsFirstMouse:(NSEvent *)event
2069 {
2070     // We hack AK's hitTest method to catch all events at the topmost WebHTMLView.  However, for
2071     // the purposes of this method we want to really query the deepest view, so we forward to it.
2072     forceRealHitTest = YES;
2073     NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]];
2074     forceRealHitTest = NO;
2075     
2076     if ([hitView isKindOfClass:[self class]]) {
2077         WebHTMLView *hitHTMLView = (WebHTMLView *)hitView;
2078         [[hitHTMLView _bridge] setActivationEventNumber:[event eventNumber]];
2079         return [self _isSelectionEvent:event] ? [[hitHTMLView _bridge] eventMayStartDrag:event] : NO;
2080     } else {
2081         return [hitView acceptsFirstMouse:event];
2082     }
2083 }
2084
2085 - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event
2086 {
2087     // We hack AK's hitTest method to catch all events at the topmost WebHTMLView.  However, for
2088     // the purposes of this method we want to really query the deepest view, so we forward to it.
2089     forceRealHitTest = YES;
2090     NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]];
2091     forceRealHitTest = NO;
2092     
2093     if ([hitView isKindOfClass:[self class]]) {
2094         WebHTMLView *hitHTMLView = (WebHTMLView *)hitView;
2095         return [self _isSelectionEvent:event] ? [[hitHTMLView _bridge] eventMayStartDrag:event] : NO;
2096     } else {
2097         return [hitView shouldDelayWindowOrderingForEvent:event];
2098     }
2099 }
2100
2101 - (void)mouseDown:(NSEvent *)event
2102 {    
2103     // TEXTINPUT: if there is marked text and the current input
2104     // manager wants to handle mouse events, we need to make sure to
2105     // pass it to them. If not, then we need to notify the input
2106     // manager when the marked text is abandoned (user clicks outside
2107     // the marked area)
2108
2109     [_private->compController endRevertingChange:NO moveLeft:NO];
2110
2111     // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here.
2112     // We don't want to pass them along to KHTML a second time.
2113     if ([event modifierFlags] & NSControlKeyMask) {
2114         return;
2115     }
2116     
2117     _private->ignoringMouseDraggedEvents = NO;
2118     
2119     // Record the mouse down position so we can determine drag hysteresis.
2120     [_private->mouseDownEvent release];
2121     _private->mouseDownEvent = [event retain];
2122
2123     // Don't do any mouseover while the mouse is down.
2124     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_updateMouseoverWithFakeEvent) object:nil];
2125
2126     // Let KHTML get a chance to deal with the event. This will call back to us
2127     // to start the autoscroll timer if appropriate.
2128     [[self _bridge] mouseDown:event];
2129 }
2130
2131 - (void)dragImage:(NSImage *)dragImage
2132                at:(NSPoint)at
2133            offset:(NSSize)offset
2134             event:(NSEvent *)event
2135        pasteboard:(NSPasteboard *)pasteboard
2136            source:(id)source
2137         slideBack:(BOOL)slideBack
2138 {   
2139     [self _stopAutoscrollTimer];
2140     
2141     _private->initiatedDrag = YES;
2142     [[self _webView] _setInitiatedDrag:YES];
2143     
2144     // Retain this view during the drag because it may be released before the drag ends.
2145     [self retain];
2146
2147     [super dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack];
2148 }
2149
2150 - (void)mouseDragged:(NSEvent *)event
2151 {
2152     // TEXTINPUT: if there is marked text and the current input
2153     // manager wants to handle mouse events, we need to make sure to
2154     // pass it to them.
2155
2156     if (!_private->ignoringMouseDraggedEvents) {
2157         [[self _bridge] mouseDragged:event];
2158     }
2159 }
2160
2161 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
2162 {
2163     if (_private->webCoreDragOp == NSDragOperationNone) {
2164         return (NSDragOperationGeneric | NSDragOperationCopy);
2165     } else {
2166         return _private->webCoreDragOp;
2167     }
2168 }
2169
2170 - (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenLoc
2171 {
2172     NSPoint windowImageLoc = [[self window] convertScreenToBase:screenLoc];
2173     NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + _private->dragOffset.x, windowImageLoc.y + _private->dragOffset.y);
2174     [[self _bridge] dragSourceMovedTo:windowMouseLoc];
2175 }
2176
2177 - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
2178 {
2179     NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint];
2180     NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + _private->dragOffset.x, windowImageLoc.y + _private->dragOffset.y);
2181     [[self _bridge] dragSourceEndedAt:windowMouseLoc operation:operation];
2182
2183     _private->initiatedDrag = NO;
2184     [[self _webView] _setInitiatedDrag:NO];
2185     
2186     // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event.
2187     _private->ignoringMouseDraggedEvents = YES;
2188     
2189     // Once the dragging machinery kicks in, we no longer get mouse drags or the up event.
2190     // khtml expects to get balanced down/up's, so we must fake up a mouseup.
2191     NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
2192                                             location:windowMouseLoc
2193                                        modifierFlags:[[NSApp currentEvent] modifierFlags]
2194                                            timestamp:[NSDate timeIntervalSinceReferenceDate]
2195                                         windowNumber:[[self window] windowNumber]
2196                                              context:[[NSApp currentEvent] context]
2197                                          eventNumber:0 clickCount:0 pressure:0];
2198     [self mouseUp:fakeEvent]; // This will also update the mouseover state.
2199     
2200     // Balance the previous retain from when the drag started.
2201     [self release];
2202 }
2203
2204 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
2205 {
2206     ASSERT(_private->draggingImageURL);
2207     
2208     NSFileWrapper *wrapper = [[self _dataSource] _fileWrapperForURL:_private->draggingImageURL];
2209     ASSERT(wrapper);    
2210     
2211     // FIXME: Report an error if we fail to create a file.
2212     NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]];
2213     path = [[NSFileManager defaultManager] _web_pathWithUniqueFilenameForPath:path];
2214     if (![wrapper writeToFile:path atomically:NO updateFilenames:YES]) {
2215         ERROR("Failed to create image file via -[NSFileWrapper writeToFile:atomically:updateFilenames:]");
2216     }
2217     
2218     return [NSArray arrayWithObject:[path lastPathComponent]];
2219 }
2220
2221 - (BOOL)_canProcessDragWithDraggingInfo:(id <NSDraggingInfo>)draggingInfo
2222 {
2223     NSPasteboard *pasteboard = [draggingInfo draggingPasteboard];
2224     NSMutableSet *types = [NSMutableSet setWithArray:[pasteboard types]];
2225     [types intersectSet:[NSSet setWithArray:[WebHTMLView _insertablePasteboardTypes]]];
2226     if ([types count] == 0) {
2227         return NO;
2228     } else if ([types count] == 1 && 
2229                [types containsObject:NSFilenamesPboardType] && 
2230                ![self _imageExistsAtPaths:[pasteboard propertyListForType:NSFilenamesPboardType]]) {
2231         return NO;
2232     }
2233     
2234     NSPoint point = [self convertPoint:[draggingInfo draggingLocation] fromView:nil];
2235     NSDictionary *element = [self elementAtPoint:point];
2236     if ([[self _webView] isEditable] || [[element objectForKey:WebElementDOMNodeKey] isContentEditable]) {
2237         if (_private->initiatedDrag && [[element objectForKey:WebElementIsSelectedKey] boolValue]) {
2238             // Can't drag onto the selection being dragged.
2239             return NO;
2240         }
2241         return YES;
2242     }
2243     
2244     return NO;
2245 }
2246
2247 - (BOOL)_isMoveDrag
2248 {
2249     return _private->initiatedDrag && 
2250     ([self _isEditable] && [self _hasSelection]) &&
2251     ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) == 0;
2252 }
2253
2254 - (NSDragOperation)draggingUpdatedWithDraggingInfo:(id <NSDraggingInfo>)draggingInfo actionMask:(unsigned int)actionMask
2255 {
2256     NSDragOperation operation = NSDragOperationNone;
2257     
2258     if (actionMask & WebDragDestinationActionDHTML) {
2259         operation = [[self _bridge] dragOperationForDraggingInfo:draggingInfo];
2260     }
2261     _private->webCoreHandlingDrag = (operation != NSDragOperationNone);
2262     
2263     if ((actionMask & WebDragDestinationActionEdit) &&
2264         !_private->webCoreHandlingDrag
2265         && [self _canProcessDragWithDraggingInfo:draggingInfo]) {
2266         WebView *webView = [self _webView];
2267         [webView moveDragCaretToPoint:[webView convertPoint:[draggingInfo draggingLocation] fromView:nil]];
2268         operation = [self _isMoveDrag] ? NSDragOperationMove : NSDragOperationCopy;
2269     } else {
2270         [[self _webView] removeDragCaret];
2271     }
2272     
2273     return operation;
2274 }
2275
2276 - (void)draggingCancelledWithDraggingInfo:(id <NSDraggingInfo>)draggingInfo
2277 {
2278     [[self _bridge] dragExitedWithDraggingInfo:draggingInfo];
2279     [[self _webView] removeDragCaret];
2280 }
2281
2282 - (BOOL)concludeDragForDraggingInfo:(id <NSDraggingInfo>)draggingInfo actionMask:(unsigned int)actionMask
2283 {
2284     WebView *webView = [self _webView];
2285     WebBridge *bridge = [self _bridge];
2286     if (_private->webCoreHandlingDrag) {
2287         ASSERT(actionMask & WebDragDestinationActionDHTML);
2288         [[webView _UIDelegateForwarder] webView:webView willPerformDragDestinationAction:WebDragDestinationActionDHTML forDraggingInfo:draggingInfo];
2289         [bridge concludeDragForDraggingInfo:draggingInfo];
2290         return YES;
2291     } else if (actionMask & WebDragDestinationActionEdit) {
2292         BOOL didInsert = NO;
2293         if ([self _canProcessDragWithDraggingInfo:draggingInfo]) {
2294             NSPasteboard *pasteboard = [draggingInfo draggingPasteboard];
2295             DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard allowPlainText:YES];
2296             if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[bridge dragCaretDOMRange] givenAction:WebViewInsertActionDropped]) {
2297                 [[webView _UIDelegateForwarder] webView:webView willPerformDragDestinationAction:WebDragDestinationActionEdit forDraggingInfo:draggingInfo];
2298                 if ([self _isMoveDrag]) {
2299                     BOOL smartMove = [[self _bridge] selectionGranularity] == WebSelectByWord && [self _canSmartReplaceWithPasteboard:pasteboard];
2300                     [bridge moveSelectionToDragCaret:fragment smartMove:smartMove];
2301                 } else {
2302                     [bridge setSelectionToDragCaret];
2303                     [bridge replaceSelectionWithFragment:fragment selectReplacement:YES smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]];
2304                 }
2305                 didInsert = YES;
2306             }
2307         }
2308         [webView removeDragCaret];
2309         return didInsert;
2310     }
2311     return NO;
2312 }
2313
2314 - (NSDictionary *)elementAtPoint:(NSPoint)point
2315 {
2316     NSDictionary *elementInfoWC = [[self _bridge] elementAtPoint:point];
2317     NSMutableDictionary *elementInfo = [elementInfoWC mutableCopy];
2318     
2319     // Convert URL strings to NSURLs
2320     [elementInfo _web_setObjectIfNotNil:[NSURL _web_URLWithDataAsString:[elementInfoWC objectForKey:WebElementLinkURLKey]] forKey:WebElementLinkURLKey];
2321     [elementInfo _web_setObjectIfNotNil:[NSURL _web_URLWithDataAsString:[elementInfoWC objectForKey:WebElementImageURLKey]] forKey:WebElementImageURLKey];
2322     
2323     WebFrameView *webFrameView = [self _web_parentWebFrameView];
2324     ASSERT(webFrameView);
2325     WebFrame *webFrame = [webFrameView webFrame];
2326     
2327     if (webFrame) {
2328         NSString *frameName = [elementInfoWC objectForKey:WebElementLinkTargetFrameKey];
2329         if ([frameName length] == 0) {
2330             [elementInfo setObject:webFrame forKey:WebElementLinkTargetFrameKey];
2331         } else {
2332             WebFrame *wf = [webFrame findFrameNamed:frameName];
2333             if (wf != nil)
2334                 [elementInfo setObject:wf forKey:WebElementLinkTargetFrameKey];
2335             else
2336                 [elementInfo removeObjectForKey:WebElementLinkTargetFrameKey];
2337         }
2338         
2339         [elementInfo setObject:webFrame forKey:WebElementFrameKey];
2340     }
2341     
2342     return [elementInfo autorelease];
2343 }
2344
2345 - (void)mouseUp:(NSEvent *)event
2346 {
2347     // TEXTINPUT: if there is marked text and the current input
2348     // manager wants to handle mouse events, we need to make sure to
2349     // pass it to them.
2350
2351     [self _stopAutoscrollTimer];
2352     [[self _bridge] mouseUp:event];
2353     [self _updateMouseoverWithFakeEvent];
2354 }
2355
2356 - (void)mouseMovedNotification:(NSNotification *)notification
2357 {
2358     [self _updateMouseoverWithEvent:[[notification userInfo] objectForKey:@"NSEvent"]];
2359 }
2360
2361 - (BOOL)supportsTextEncoding
2362 {
2363     return YES;
2364 }
2365
2366 - (NSView *)nextValidKeyView
2367 {
2368     NSView *view = nil;
2369     if (![self isHiddenOrHasHiddenAncestor]) {
2370         view = [[self _bridge] nextKeyViewInsideWebFrameViews];
2371     }
2372     if (view == nil) {
2373         view = [super nextValidKeyView];
2374     }
2375     return view;
2376 }
2377
2378 - (NSView *)previousValidKeyView
2379 {
2380     NSView *view = nil;
2381     if (![self isHiddenOrHasHiddenAncestor]) {
2382         view = [[self _bridge] previousKeyViewInsideWebFrameViews];
2383     }
2384     if (view == nil) {
2385         view = [super previousValidKeyView];
2386     }
2387     return view;
2388 }
2389
2390 - (BOOL)becomeFirstResponder
2391 {
2392     NSView *view = nil;
2393     if (![[self _webView] _isPerformingProgrammaticFocus]) {
2394         switch ([[self window] keyViewSelectionDirection]) {
2395         case NSDirectSelection:
2396             break;
2397         case NSSelectingNext:
2398             view = [[self _bridge] nextKeyViewInsideWebFrameViews];
2399             break;
2400         case NSSelectingPrevious:
2401             view = [[self _bridge] previousKeyViewInsideWebFrameViews];
2402             break;
2403         }
2404     }
2405     if (view) {
2406         [[self window] makeFirstResponder:view];
2407     }
2408     [self updateFocusDisplay];
2409     _private->startNewKillRingSequence = YES;
2410     return YES;
2411 }
2412
2413 // This approach could be relaxed when dealing with 3228554.
2414 // Some alteration to the selection behavior was done to deal with 3672088.
2415 - (BOOL)resignFirstResponder
2416 {
2417     BOOL resign = [super resignFirstResponder];
2418     if (resign) {
2419         [_private->compController endRevertingChange:NO moveLeft:NO];
2420         _private->resigningFirstResponder = YES;
2421         if (![self maintainsInactiveSelection]) { 
2422             if ([[self _webView] _isPerformingProgrammaticFocus]) {
2423                 [self deselectText];
2424             }
2425             else {
2426                 [self deselectAll];
2427             }
2428         }
2429         [self updateFocusDisplay];
2430         _private->resigningFirstResponder = NO;
2431     }
2432     return resign;
2433 }
2434
2435 //------------------------------------------------------------------------------------
2436 // WebDocumentView protocol
2437 //------------------------------------------------------------------------------------
2438 - (void)setDataSource:(WebDataSource *)dataSource 
2439 {
2440 }
2441
2442 - (void)dataSourceUpdated:(WebDataSource *)dataSource
2443 {
2444 }
2445
2446 // Does setNeedsDisplay:NO as a side effect when printing is ending.
2447 // pageWidth != 0 implies we will relayout to a new width
2448 - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize
2449 {
2450     WebFrame *frame = [self _frame];
2451     NSArray *subframes = [frame childFrames];
2452     unsigned n = [subframes count];
2453     unsigned i;
2454     for (i = 0; i != n; ++i) {
2455         WebFrame *subframe = [subframes objectAtIndex:i];
2456         WebFrameView *frameView = [subframe frameView];
2457         if ([[subframe dataSource] _isDocumentHTML]) {
2458             [(WebHTMLView *)[frameView documentView] _setPrinting:printing minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:adjustViewSize];
2459         }
2460     }
2461
2462     if (printing != _private->printing) {
2463         [_private->pageRects release];
2464         _private->pageRects = nil;
2465         _private->printing = printing;
2466         [self setNeedsToApplyStyles:YES];
2467         [self setNeedsLayout:YES];
2468         [self layoutToMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize];
2469         if (printing) {
2470             [[self _webView] _adjustPrintingMarginsForHeaderAndFooter];
2471         } else {
2472             // Can't do this when starting printing or nested printing won't work, see 3491427.
2473             [self setNeedsDisplay:NO];
2474         }
2475     }
2476 }
2477
2478 // This is needed for the case where the webview is embedded in the view that's being printed.
2479 // It shouldn't be called when the webview is being printed directly.
2480 - (void)adjustPageHeightNew:(float *)newBottom top:(float)oldTop bottom:(float)oldBottom limit:(float)bottomLimit
2481 {
2482     // This helps when we print as part of a larger print process.
2483     // If the WebHTMLView itself is what we're printing, then we will never have to do this.
2484     BOOL wasInPrintingMode = _private->printing;
2485     if (!wasInPrintingMode) {
2486         [self _setPrinting:YES minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:NO];
2487     }
2488     
2489     [[self _bridge] adjustPageHeightNew:newBottom top:oldTop bottom:oldBottom limit:bottomLimit];
2490     
2491     if (!wasInPrintingMode) {
2492         [self _setPrinting:NO minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:NO];
2493     }
2494 }
2495
2496 - (float)_availablePaperWidthForPrintOperation:(NSPrintOperation *)printOperation
2497 {
2498     NSPrintInfo *printInfo = [printOperation printInfo];
2499     return [printInfo paperSize].width - [printInfo leftMargin] - [printInfo rightMargin];
2500 }
2501
2502 - (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation
2503 {
2504     float viewWidth = NSWidth([self bounds]);
2505     if (viewWidth < 1) {
2506         ERROR("%@ has no width when printing", self);
2507         return 1.0;
2508     }
2509
2510     float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
2511     float maxShrinkToFitScaleFactor = 1/PrintingMaximumShrinkFactor;
2512     float shrinkToFitScaleFactor = [self _availablePaperWidthForPrintOperation:printOperation]/viewWidth;
2513     return userScaleFactor * MAX(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor);
2514 }
2515
2516 // FIXME 3491344: This is a secret AppKit-internal method that we need to override in order
2517 // to get our shrink-to-fit to work with a custom pagination scheme. We can do this better
2518 // if AppKit makes it SPI/API.
2519 - (float)_provideTotalScaleFactorForPrintOperation:(NSPrintOperation *)printOperation 
2520 {
2521     return [self _scaleFactorForPrintOperation:printOperation];
2522 }
2523
2524 - (void)setPageWidthForPrinting:(float)pageWidth
2525 {
2526     [self _setPrinting:NO minimumPageWidth:0. maximumPageWidth:0. adjustViewSize:NO];
2527     [self _setPrinting:YES minimumPageWidth:pageWidth maximumPageWidth:pageWidth adjustViewSize:YES];
2528 }
2529
2530
2531 // Return the number of pages available for printing
2532 - (BOOL)knowsPageRange:(NSRangePointer)range {
2533     // Must do this explicit display here, because otherwise the view might redisplay while the print
2534     // sheet was up, using printer fonts (and looking different).
2535     [self displayIfNeeded];
2536     [[self window] setAutodisplay:NO];
2537     
2538     // If we are a frameset just print with the layout we have onscreen, otherwise relayout
2539     // according to the paper size
2540     float minLayoutWidth = 0.0;
2541     float maxLayoutWidth = 0.0;
2542     if (![[self _bridge] isFrameSet]) {
2543         float paperWidth = [self _availablePaperWidthForPrintOperation:[NSPrintOperation currentOperation]];
2544         minLayoutWidth = paperWidth*PrintingMinimumShrinkFactor;
2545         maxLayoutWidth = paperWidth*PrintingMaximumShrinkFactor;
2546     }
2547     [self _setPrinting:YES minimumPageWidth:minLayoutWidth maximumPageWidth:maxLayoutWidth adjustViewSize:YES]; // will relayout
2548     
2549     // There is a theoretical chance that someone could do some drawing between here and endDocument,
2550     // if something caused setNeedsDisplay after this point. If so, it's not a big tragedy, because
2551     // you'd simply see the printer fonts on screen. As of this writing, this does not happen with Safari.
2552
2553     range->location = 1;
2554     NSPrintOperation *printOperation = [NSPrintOperation currentOperation];
2555     float totalScaleFactor = [self _scaleFactorForPrintOperation:printOperation];
2556     float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
2557     [_private->pageRects release];
2558     NSArray *newPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor
2559                                                                           printHeight:[self _calculatePrintHeight]/totalScaleFactor];
2560     // AppKit gets all messed up if you give it a zero-length page count (see 3576334), so if we
2561     // hit that case we'll pass along a degenerate 1 pixel square to print. This will print
2562     // a blank page (with correct-looking header and footer if that option is on), which matches
2563     // the behavior of IE and Camino at least.
2564     if ([newPageRects count] == 0) {
2565         newPageRects = [NSArray arrayWithObject:[NSValue valueWithRect: NSMakeRect(0, 0, 1, 1)]];
2566     }
2567     _private->pageRects = [newPageRects retain];
2568     
2569     range->length = [_private->pageRects count];
2570     
2571     return YES;
2572 }
2573
2574 // Return the drawing rectangle for a particular page number
2575 - (NSRect)rectForPage:(int)page {
2576     return [[_private->pageRects objectAtIndex: (page-1)] rectValue];
2577 }
2578
2579 - (void)drawPageBorderWithSize:(NSSize)borderSize
2580 {
2581     ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize]));    
2582     [[self _webView] _drawHeaderAndFooter];
2583 }
2584
2585 - (void)endDocument
2586 {
2587     [super endDocument];
2588     // Note sadly at this point [NSGraphicsContext currentContextDrawingToScreen] is still NO 
2589     [self _setPrinting:NO minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:YES];
2590     [[self window] setAutodisplay:YES];
2591 }
2592
2593 - (BOOL)performKeyEquivalent:(NSEvent *)event
2594 {
2595     // Pass command-key combos through WebCore if there is a key binding available for
2596     // this event. This lets web pages have a crack at intercepting command-modified keypresses.
2597     if ([self _web_firstResponderIsSelfOrDescendantView] && [[self _bridge] interceptKeyEvent:event toView:self]) {
2598         return YES;
2599     }
2600     return [super performKeyEquivalent:event];
2601 }
2602
2603 - (BOOL)_interceptEditingKeyEvent:(NSEvent *)event
2604 {   
2605     // Work around this bug:
2606     // <rdar://problem/3630640>: "Calling interpretKeyEvents: in a custom text view can fail to process keys right after app startup"
2607     [NSKeyBindingManager sharedKeyBindingManager];
2608     
2609     // Use the isEditable state to determine whether or not to process tab key events.
2610     // The idea here is that isEditable will be NO when this WebView is being used
2611     // in a browser, and we desire the behavior where tab moves to the next element
2612     // in tab order. If isEditable is YES, it is likely that the WebView is being
2613     // embedded as the whole view, as in Mail, and tabs should input tabs as expected
2614     // in a text editor.
2615     if (![[self _webView] isEditable] && [event _web_isTabKeyEvent]) 
2616         return NO;
2617     
2618     // Now process the key normally
2619     [self interpretKeyEvents:[NSArray arrayWithObject:event]];
2620     return YES;
2621 }
2622
2623 - (void)keyDown:(NSEvent *)event
2624 {
2625     BOOL callSuper = NO;
2626
2627     _private->keyDownEvent = event;
2628
2629     WebBridge *bridge = [self _bridge];
2630     if ([bridge interceptKeyEvent:event toView:self]) {
2631         // WebCore processed a key event, bail on any outstanding complete: UI
2632         [_private->compController endRevertingChange:YES moveLeft:NO];
2633     } else if (_private->compController && [_private->compController filterKeyDown:event]) {
2634         // Consumed by complete: popup window
2635     } else {
2636         // We're going to process a key event, bail on any outstanding complete: UI
2637         [_private->compController endRevertingChange:YES moveLeft:NO];
2638         if ([self _canEdit] && [self _interceptEditingKeyEvent:event]) {
2639             // Consumed by key bindings manager.
2640         } else {
2641             callSuper = YES;
2642         }
2643     }
2644     if (callSuper) {
2645         [super keyDown:event];
2646     } else {
2647         [NSCursor setHiddenUntilMouseMoves:YES];
2648     }
2649
2650     _private->keyDownEvent = nil;
2651 }
2652
2653 - (void)keyUp:(NSEvent *)event
2654 {
2655     if (![[self _bridge] interceptKeyEvent:event toView:self]) {
2656         [super keyUp:event];
2657     }
2658 }
2659
2660 - (id)accessibilityAttributeValue:(NSString*)attributeName
2661 {
2662     if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) {
2663         id accTree = [[self _bridge] accessibilityTree];
2664         if (accTree)
2665             return [NSArray arrayWithObject: accTree];
2666         return nil;
2667     }
2668     return [super accessibilityAttributeValue:attributeName];
2669 }
2670
2671 - (id)accessibilityHitTest:(NSPoint)point
2672 {
2673     id accTree = [[self _bridge] accessibilityTree];
2674     if (accTree) {
2675         NSPoint windowCoord = [[self window] convertScreenToBase: point];
2676         return [accTree accessibilityHitTest: [self convertPoint:windowCoord fromView:nil]];
2677     }
2678     else
2679         return self;
2680 }
2681
2682 - (void)centerSelectionInVisibleArea:(id)sender
2683 {
2684     // FIXME: Does this do the right thing when the selection is not a caret?
2685     [[self _bridge] ensureCaretVisible];
2686 }
2687
2688 - (void)_alterCurrentSelection:(WebSelectionAlteration)alteration direction:(WebSelectionDirection)direction granularity:(WebSelectionGranularity)granularity
2689 {
2690     WebBridge *bridge = [self _bridge];
2691     DOMRange *proposedRange = [bridge rangeByAlteringCurrentSelection:alteration direction:direction granularity:granularity];
2692     WebView *webView = [self _webView];
2693     if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:proposedRange affinity:[bridge selectionAffinity] stillSelecting:NO]) {
2694         [bridge alterCurrentSelection:alteration direction:direction granularity:granularity];
2695     }
2696 }
2697
2698 - (void)_alterCurrentSelection:(WebSelectionAlteration)alteration verticalDistance:(float)verticalDistance
2699 {
2700     WebBridge *bridge = [self _bridge];
2701     DOMRange *proposedRange = [bridge rangeByAlteringCurrentSelection:alteration verticalDistance:verticalDistance];
2702     WebView *webView = [self _webView];
2703     if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:proposedRange affinity:[bridge selectionAffinity] stillSelecting:NO]) {
2704         [bridge alterCurrentSelection:alteration verticalDistance:verticalDistance];
2705     }
2706 }
2707
2708 - (void)moveBackward:(id)sender
2709 {
2710     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectByCharacter];
2711 }
2712
2713 - (void)moveBackwardAndModifySelection:(id)sender
2714 {
2715     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectByCharacter];
2716 }
2717
2718 - (void)moveDown:(id)sender
2719 {
2720     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectByLine];
2721 }
2722
2723 - (void)moveDownAndModifySelection:(id)sender
2724 {
2725     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectByLine];
2726 }
2727
2728 - (void)moveForward:(id)sender
2729 {
2730     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectByCharacter];
2731 }
2732
2733 - (void)moveForwardAndModifySelection:(id)sender
2734 {
2735     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectByCharacter];
2736 }
2737
2738 - (void)moveLeft:(id)sender
2739 {
2740     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectLeft granularity:WebSelectByCharacter];
2741 }
2742
2743 - (void)moveLeftAndModifySelection:(id)sender
2744 {
2745     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectLeft granularity:WebSelectByCharacter];
2746 }
2747
2748 - (void)moveRight:(id)sender
2749 {
2750     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectRight granularity:WebSelectByCharacter];
2751 }
2752
2753 - (void)moveRightAndModifySelection:(id)sender
2754 {
2755     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectRight granularity:WebSelectByCharacter];
2756 }
2757
2758 - (void)moveToBeginningOfDocument:(id)sender
2759 {
2760     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectToDocumentBoundary];
2761 }
2762
2763 - (void)moveToBeginningOfDocumentAndModifySelection:(id)sender
2764 {
2765     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectToDocumentBoundary];
2766 }
2767
2768 - (void)moveToBeginningOfLine:(id)sender
2769 {
2770     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectToLineBoundary];
2771 }
2772
2773 - (void)moveToBeginningOfLineAndModifySelection:(id)sender
2774 {
2775     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectToLineBoundary];
2776 }
2777
2778 - (void)moveToBeginningOfParagraph:(id)sender
2779 {
2780     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectToParagraphBoundary];
2781 }
2782
2783 - (void)moveToBeginningOfParagraphAndModifySelection:(id)sender
2784 {
2785     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectToParagraphBoundary];
2786 }
2787
2788 - (void)moveToEndOfDocument:(id)sender
2789 {
2790     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectToDocumentBoundary];
2791 }
2792
2793 - (void)moveToEndOfDocumentAndModifySelection:(id)sender
2794 {
2795     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectToDocumentBoundary];
2796 }
2797
2798 - (void)moveToEndOfLine:(id)sender
2799 {
2800     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectToLineBoundary];
2801 }
2802
2803 - (void)moveToEndOfLineAndModifySelection:(id)sender
2804 {
2805     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectToLineBoundary];
2806 }
2807
2808 - (void)moveToEndOfParagraph:(id)sender
2809 {
2810     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectToParagraphBoundary];
2811 }
2812
2813 - (void)moveToEndOfParagraphAndModifySelection:(id)sender
2814 {
2815     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectToParagraphBoundary];
2816 }
2817
2818 - (void)moveParagraphBackwardAndModifySelection:(id)sender
2819 {
2820     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectByParagraph];
2821 }
2822
2823 - (void)moveParagraphForwardAndModifySelection:(id)sender
2824 {
2825     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectByParagraph];
2826 }
2827
2828 - (void)moveUp:(id)sender
2829 {
2830     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectByLine];
2831 }
2832
2833 - (void)moveUpAndModifySelection:(id)sender
2834 {
2835     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectByLine];
2836 }
2837
2838 - (void)moveWordBackward:(id)sender
2839 {
2840     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectByWord];
2841 }
2842
2843 - (void)moveWordBackwardAndModifySelection:(id)sender
2844 {
2845     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectByWord];
2846 }
2847
2848 - (void)moveWordForward:(id)sender
2849 {
2850     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectByWord];
2851 }
2852
2853 - (void)moveWordForwardAndModifySelection:(id)sender
2854 {
2855     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectByWord];
2856 }
2857
2858 - (void)moveWordLeft:(id)sender
2859 {
2860     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectLeft granularity:WebSelectByWord];
2861 }
2862
2863 - (void)moveWordLeftAndModifySelection:(id)sender
2864 {
2865     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectLeft granularity:WebSelectByWord];
2866 }
2867
2868 - (void)moveWordRight:(id)sender
2869 {
2870     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectRight granularity:WebSelectByWord];
2871 }
2872
2873 - (void)moveWordRightAndModifySelection:(id)sender
2874 {
2875     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectRight granularity:WebSelectByWord];
2876 }
2877
2878 - (void)pageUp:(id)sender
2879 {
2880     WebFrameView *frameView = [self _web_parentWebFrameView];
2881     if (frameView == nil)
2882         return;
2883     [self _alterCurrentSelection:WebSelectByMoving verticalDistance:-[frameView _verticalPageScrollDistance]];
2884 }
2885
2886 - (void)pageDown:(id)sender
2887 {
2888     WebFrameView *frameView = [self _web_parentWebFrameView];
2889     if (frameView == nil)
2890         return;
2891     [self _alterCurrentSelection:WebSelectByMoving verticalDistance:[frameView _verticalPageScrollDistance]];
2892 }
2893
2894 - (void)pageUpAndModifySelection:(id)sender
2895 {
2896     WebFrameView *frameView = [self _web_parentWebFrameView];
2897     if (frameView == nil)
2898         return;
2899     [self _alterCurrentSelection:WebSelectByExtending verticalDistance:-[frameView _verticalPageScrollDistance]];
2900 }
2901
2902 - (void)pageDownAndModifySelection:(id)sender
2903 {
2904     WebFrameView *frameView = [self _web_parentWebFrameView];
2905     if (frameView == nil)
2906         return;
2907     [self _alterCurrentSelection:WebSelectByExtending verticalDistance:[frameView _verticalPageScrollDistance]];
2908 }
2909
2910 - (void)_expandSelectionToGranularity:(WebSelectionGranularity)granularity
2911 {
2912     WebBridge *bridge = [self _bridge];
2913     DOMRange *range = [bridge rangeByExpandingSelectionWithGranularity:granularity];
2914     if (range && ![range collapsed]) {
2915         WebView *webView = [self _webView];
2916         if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:range affinity:[bridge selectionAffinity] stillSelecting:NO]) {
2917             [bridge setSelectedDOMRange:range affinity:[bridge selectionAffinity]];
2918         }
2919     }
2920 }
2921
2922 - (void)selectParagraph:(id)sender
2923 {
2924     [self _expandSelectionToGranularity:WebSelectByParagraph];
2925 }
2926
2927 - (void)selectLine:(id)sender
2928 {
2929     [self _expandSelectionToGranularity:WebSelectByLine];
2930 }
2931
2932 - (void)selectWord:(id)sender
2933 {
2934     [self _expandSelectionToGranularity:WebSelectByWord];
2935 }
2936
2937 - (void)copy:(id)sender
2938 {
2939     if ([[self _bridge] tryDHTMLCopy]) {
2940         return;     // DHTML did the whole operation
2941     }
2942     if (![self _canCopy]) {
2943         NSBeep();
2944         return;
2945     }
2946     [self _writeSelectionToPasteboard:[NSPasteboard generalPasteboard]];
2947 }
2948
2949 - (void)delete:(id)sender
2950 {
2951     if (![self _canDelete]) {
2952         NSBeep();
2953         return;
2954     }
2955     [self _deleteSelection];
2956 }
2957
2958 - (void)cut:(id)sender
2959 {
2960     WebBridge *bridge = [self _bridge];
2961     if ([bridge tryDHTMLCut]) {
2962         return;     // DHTML did the whole operation
2963     }
2964     if (![self _canCut]) {
2965         NSBeep();
2966         return;
2967     }
2968     DOMRange *range = [self _selectedRange];
2969     if ([self _shouldDeleteRange:range]) {
2970         [self _writeSelectionToPasteboard:[NSPasteboard generalPasteboard]];
2971         [bridge deleteSelectionWithSmartDelete:[self _canSmartCopyOrDelete]];
2972    }
2973 }
2974
2975 - (void)paste:(id)sender
2976 {
2977     if ([[self _bridge] tryDHTMLPaste]) {
2978         return;     // DHTML did the whole operation
2979     }
2980     if (![self _canPaste]) {
2981         return;
2982     }
2983     [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:YES];
2984 }
2985
2986 - (NSDictionary *)_selectionFontAttributes
2987 {
2988     NSFont *font = [[self _bridge] fontForSelection:NULL];
2989     if (font == nil)
2990         return nil;
2991     return [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
2992 }
2993
2994 - (NSData *)_selectionFontAttributesAsRTF
2995 {
2996     NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"x" attributes:[self _selectionFontAttributes]];
2997     NSData *data = [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:nil];
2998     [string release];
2999     return data;
3000 }
3001
3002 - (NSDictionary *)_fontAttributesFromFontPasteboard
3003 {
3004     NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
3005     if (fontPasteboard == nil)
3006         return nil;
3007     NSData *data = [fontPasteboard dataForType:NSFontPboardType];
3008     if (data == nil || [data length] == 0)
3009         return nil;
3010     // NSTextView does something more efficient by parsing the attributes only, but that's not available in API.
3011     NSAttributedString *string = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease];
3012     if (string == nil || [string length] == 0)
3013         return nil;
3014     return [string fontAttributesInRange:NSMakeRange(0, 1)];
3015 }
3016
3017 - (DOMCSSStyleDeclaration *)_emptyStyle
3018 {
3019     return [[[self _bridge] DOMDocument] createCSSStyleDeclaration];
3020 }
3021
3022 - (DOMCSSStyleDeclaration *)_styleFromFontAttributes:(NSDictionary *)dictionary
3023 {
3024     DOMCSSStyleDeclaration *style = [self _emptyStyle];
3025
3026     NSFont *font = [dictionary objectForKey:NSFontAttributeName];
3027     if (font != nil) {
3028         NSFontManager *fm = [NSFontManager sharedFontManager];
3029         [style setFontFamily:[font familyName]];
3030         [style setFontSize:[NSString stringWithFormat:@"%0.fpx", [font pointSize]]];
3031         if ([fm weightOfFont:font] >= 9) {
3032             [style setFontWeight:@"bold"];
3033         } else {
3034             [style setFontWeight:@"normal"];
3035         }
3036         if (([fm traitsOfFont:font] & NSItalicFontMask) != 0) {
3037             [style setFontStyle:@"italic"];
3038         } else {
3039             [style setFontStyle:@"normal"];
3040         }
3041     }
3042
3043     return style;
3044 }
3045
3046 - (void)_applyStyleToSelection:(DOMCSSStyleDeclaration *)style
3047 {
3048     if (style == nil || [style length] == 0 || ![self _canEdit])
3049         return;
3050     WebView *webView = [self _webView];
3051     WebBridge *bridge = [self _bridge];
3052     if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:[self _selectedRange]]) {
3053         [bridge applyStyle:style];
3054     }
3055 }
3056
3057 - (void)copyFont:(id)sender
3058 {
3059     // Put RTF with font attributes on the pasteboard.
3060     // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
3061     NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
3062     [fontPasteboard declareTypes:[NSArray arrayWithObject:NSFontPboardType] owner:nil];
3063     [fontPasteboard setData:[self _selectionFontAttributesAsRTF] forType:NSFontPboardType];
3064 }
3065
3066 - (void)pasteFont:(id)sender
3067 {
3068     // Read RTF with font attributes from the pasteboard.
3069     // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
3070     [self _applyStyleToSelection:[self _styleFromFontAttributes:[self _fontAttributesFromFontPasteboard]]];
3071 }
3072
3073 - (void)pasteAsPlainText:(id)sender
3074 {
3075     if (![self _canEdit])
3076         return;
3077         
3078     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
3079     NSString *text = [pasteboard stringForType:NSStringPboardType];
3080     WebBridge *bridge = [self _bridge];
3081     if ([self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionPasted]) {
3082         [bridge replaceSelectionWithText:text selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]];
3083     }
3084 }
3085
3086 - (void)pasteAsRichText:(id)sender
3087 {
3088     // Since rich text always beats plain text when both are on the pasteboard, it's not
3089     // clear how this is different from plain old paste.
3090     [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO];
3091 }
3092
3093 - (NSFont *)_originalFontA
3094 {
3095     return [[NSFontManager sharedFontManager] fontWithFamily:@"Helvetica" traits:0 weight:5 size:10];
3096 }
3097
3098 - (NSFont *)_originalFontB
3099 {
3100     return [[NSFontManager sharedFontManager] fontWithFamily:@"Times" traits:(NSBoldFontMask | NSItalicFontMask) weight:10 size:12];
3101 }
3102
3103 - (void)_addToStyle:(DOMCSSStyleDeclaration *)style fontA:(NSFont *)a fontB:(NSFont *)b
3104 {
3105     if (a == nil || b == nil)
3106         return;
3107
3108     NSFontManager *fm = [NSFontManager sharedFontManager];
3109
3110     NSFont *oa = [self _originalFontA];
3111
3112     NSString *fa = [a familyName];
3113     NSString *fb = [b familyName];
3114     if ([fa isEqualToString:fb]) {
3115         [style setFontFamily:fa];
3116     }
3117
3118     int sa = [a pointSize];
3119     int sb = [b pointSize];
3120     int soa = [oa pointSize];
3121     if (sa == sb) {
3122         [style setFontSize:[NSString stringWithFormat:@"%dpx", sa]];
3123     } else if (sa < soa) {
3124         // FIXME: set up a style to tell WebCore to make the font in the selection 1 pixel smaller
3125     } else if (sa > soa) {
3126         // FIXME: set up a style to tell WebCore to make the font in the selection 1 pixel larger
3127     }
3128
3129     int wa = [fm weightOfFont:a];
3130     int wb = [fm weightOfFont:b];
3131     if (wa == wb) {
3132         if (wa >= 9) {
3133             [style setFontWeight:@"bold"];
3134         } else {
3135             [style setFontWeight:@"normal"];
3136         }
3137     }
3138
3139     BOOL ia = ([fm traitsOfFont:a] & NSItalicFontMask) != 0;
3140     BOOL ib = ([fm traitsOfFont:b] & NSItalicFontMask) != 0;
3141     if (ia == ib) {
3142         if (ia) {
3143             [style setFontStyle:@"italic"];
3144         } else {
3145             [style setFontStyle:@"normal"];
3146         }
3147     }
3148 }
3149
3150 - (DOMCSSStyleDeclaration *)_styleFromFontManagerOperation
3151 {
3152     DOMCSSStyleDeclaration *style = [self _emptyStyle];
3153
3154     NSFontManager *fm = [NSFontManager sharedFontManager];
3155
3156     NSFont *oa = [self _originalFontA];
3157     NSFont *ob = [self _originalFontB];    
3158     [self _addToStyle:style fontA:[fm convertFont:oa] fontB:[fm convertFont:ob]];
3159
3160     return style;
3161 }
3162
3163 - (void)changeFont:(id)sender
3164 {
3165     [self _applyStyleToSelection:[self _styleFromFontManagerOperation]];
3166 }
3167
3168 - (NSString *)_colorAsString:(NSColor *)color
3169 {
3170     NSColor *rgbColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
3171     // FIXME: If color is non-nil and rgbColor is nil, that means we got some kind
3172     // of fancy color that can't be converted to RGB. Changing that to "transparent"
3173     // might not be great, but it's probably OK.
3174     if (rgbColor == nil)
3175         return @"transparent";
3176     float r = [rgbColor redComponent];
3177     float g = [rgbColor greenComponent];
3178     float b = [rgbColor blueComponent];
3179     float a = [rgbColor alphaComponent];
3180     if (a == 0)
3181         return @"transparent";
3182     if (r == 0 && g == 0 && b == 0 && a == 1)
3183         return @"black";
3184     if (r == 1 && g == 1 && b == 1 && a == 1)
3185         return @"white";
3186     // FIXME: Lots more named colors. Maybe we could use the table in WebCore?
3187     if (a == 1)
3188         return [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", r * 255, g * 255, b * 255];
3189     return [NSString stringWithFormat:@"rgba(%.0f,%.0f,%.0f,%f)", r * 255, g * 255, b * 255, a];
3190 }
3191
3192 - (NSString *)_shadowAsString:(NSShadow *)shadow
3193 {
3194     if (shadow == nil)
3195         return @"none";
3196     NSSize offset = [shadow shadowOffset];
3197     float blurRadius = [shadow shadowBlurRadius];
3198     if (offset.width == 0 && offset.height == 0 && blurRadius == 0)
3199         return @"none";
3200     NSColor *color = [shadow shadowColor];
3201     if (color == nil)
3202         return @"none";
3203     // FIXME: Handle non-integral values here?
3204     if (blurRadius == 0)
3205         return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height];
3206     return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height, blurRadius];
3207 }
3208
3209 - (DOMCSSStyleDeclaration *)_styleForAttributeChange:(id)sender
3210 {
3211     DOMCSSStyleDeclaration *style = [self _emptyStyle];
3212
3213     NSShadow *shadow = [[NSShadow alloc] init];
3214     [shadow setShadowOffset:NSMakeSize(1, 1)];
3215
3216     NSDictionary *oa = [NSDictionary dictionaryWithObjectsAndKeys:
3217         [self _originalFontA], NSFontAttributeName,
3218         nil];
3219     NSDictionary *ob = [NSDictionary dictionaryWithObjectsAndKeys:
3220         [NSColor blackColor], NSBackgroundColorAttributeName,
3221         [self _originalFontB], NSFontAttributeName,
3222         [NSColor whiteColor], NSForegroundColorAttributeName,
3223         shadow, NSShadowAttributeName,
3224         [NSNumber numberWithInt:NSUnderlineStyleSingle], NSStrikethroughStyleAttributeName,
3225         [NSNumber numberWithInt:1], NSSuperscriptAttributeName,
3226         [NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName,
3227         nil];
3228
3229     [shadow release];
3230
3231 #if 0
3232
3233 NSObliquenessAttributeName        /* float; skew to be applied to glyphs, default 0: no skew */
3234     // font-style, but that is just an on-off switch
3235
3236 NSExpansionAttributeName          /* float; log of expansion factor to be applied to glyphs, default 0: no expansion */
3237     // font-stretch?
3238
3239 NSKernAttributeName               /* float, amount to modify default kerning, if 0, kerning off */
3240     // letter-spacing? probably not good enough
3241
3242 NSUnderlineColorAttributeName     /* NSColor, default nil: same as foreground color */
3243 NSStrikethroughColorAttributeName /* NSColor, default nil: same as foreground color */
3244     // text-decoration-color?
3245
3246 NSLigatureAttributeName           /* int, default 1: default ligatures, 0: no ligatures, 2: all ligatures */
3247 NSBaselineOffsetAttributeName     /* float, in points; offset from baseline, default 0 */
3248 NSStrokeWidthAttributeName        /* float, in percent of font point size, default 0: no stroke; positive for stroke alone, negative for stroke and fill (a typical value for outlined text would be 3.0) */
3249 NSStrokeColorAttributeName        /* NSColor, default nil: same as foreground color */
3250     // need extensions?
3251
3252 #endif
3253     
3254     NSDictionary *a = [sender convertAttributes:oa];
3255     NSDictionary *b = [sender convertAttributes:ob];
3256
3257     NSColor *ca = [a objectForKey:NSBackgroundColorAttributeName];
3258     NSColor *cb = [b objectForKey:NSBackgroundColorAttributeName];
3259     if (ca == cb) {
3260         [style setBackgroundColor:[self _colorAsString:ca]];
3261     }
3262
3263     [self _addToStyle:style fontA:[a objectForKey:NSFontAttributeName] fontB:[b objectForKey:NSFontAttributeName]];
3264
3265     ca = [a objectForKey:NSForegroundColorAttributeName];
3266     cb = [b objectForKey:NSForegroundColorAttributeName];
3267     if (ca == cb) {
3268         [style setColor:[self _colorAsString:ca]];
3269     }
3270
3271     NSShadow *sha = [a objectForKey:NSShadowAttributeName];
3272     if (sha) {
3273         [style setTextShadow:[self _shadowAsString:sha]];
3274     } else if ([b objectForKey:NSShadowAttributeName] == nil) {
3275         [style setTextShadow:@"none"];
3276     }
3277
3278     int sa = [[a objectForKey:NSStrikethroughStyleAttributeName] intValue];
3279     int sb = [[b objectForKey:NSStrikethroughStyleAttributeName] intValue];
3280     if (sa == sb) {
3281         if (sa == NSUnderlineStyleNone)
3282             [style setTextDecoration:@"none"]; // we really mean "no line-through" rather than "none"
3283         else
3284             [style setTextDecoration:@"line-through"]; // we really mean "add line-through" rather than "line-through"
3285     }
3286
3287     sa = [[a objectForKey:NSSuperscriptAttributeName] intValue];
3288     sb = [[b objectForKey:NSSuperscriptAttributeName] intValue];
3289     if (sa == sb) {
3290         if (sa > 0)
3291             [style setVerticalAlign:@"super"];
3292         else if (sa < 0)
3293             [style setVerticalAlign:@"sub"];
3294         else
3295             [style setVerticalAlign:@"baseline"];
3296     }
3297
3298     int ua = [[a objectForKey:NSUnderlineStyleAttributeName] intValue];
3299     int ub = [[b objectForKey:NSUnderlineStyleAttributeName] intValue];
3300     if (ua == ub) {
3301         if (ua == NSUnderlineStyleNone)
3302             [style setTextDecoration:@"none"]; // we really mean "no underline" rather than "none"
3303         else
3304             [style setTextDecoration:@"underline"]; // we really mean "add underline" rather than "underline"
3305     }
3306
3307     return style;
3308 }
3309
3310 - (void)changeAttributes:(id)sender
3311 {
3312     [self _applyStyleToSelection:[self _styleForAttributeChange:sender]];
3313 }
3314
3315 - (DOMCSSStyleDeclaration *)_styleFromColorPanelWithSelector:(SEL)selector
3316 {
3317     DOMCSSStyleDeclaration *style = [self _emptyStyle];
3318
3319     ASSERT([style respondsToSelector:selector]);
3320     [style performSelector:selector withObject:[self _colorAsString:[[NSColorPanel sharedColorPanel] color]]];
3321     
3322     return style;
3323 }
3324
3325 - (void)_changeCSSColorUsingSelector:(SEL)selector inRange:(DOMRange *)range
3326 {
3327     DOMCSSStyleDeclaration *style = [self _styleFromColorPanelWithSelector:selector];
3328     WebView *webView = [self _webView];
3329     if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:range]) {
3330         [[self _bridge] applyStyle:style];
3331     }
3332 }
3333
3334 - (void)changeDocumentBackgroundColor:(id)sender
3335 {
3336     // Mimicking NSTextView, this method sets the background color for the
3337     // entire document. There is no NSTextView API for setting the background
3338     // color on the selected range only. Note that this method is currently
3339     // never called from the UI (see comment in changeColor:).
3340     // FIXME: this actually has no effect when called, probably due to 3654850. _documentRange seems
3341     // to do the right thing because it works in startSpeaking:, and I know setBackgroundColor: does the
3342     // right thing because I tested it with [self _selectedRange].
3343     // FIXME: This won't actually apply the style to the entire range here, because it ends up calling
3344     // [bridge applyStyle:], which operates on the current selection. To make this work right, we'll
3345     // need to save off the selection, temporarily set it to the entire range, make the change, then
3346     // restore the old selection.
3347     [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _documentRange]];
3348 }
3349
3350 - (void)changeColor:(id)sender
3351 {
3352     // FIXME: in NSTextView, this method calls changeDocumentBackgroundColor: when a
3353     // private call has earlier been made by [NSFontFontEffectsBox changeColor:], see 3674493. 
3354     // AppKit will have to be revised to allow this to work with anything that isn't an 
3355     // NSTextView. However, this might not be required for Tiger, since the background-color 
3356     // changing box in the font panel doesn't work in Mail (3674481), though it does in TextEdit.
3357     [self _applyStyleToSelection:[self _styleFromColorPanelWithSelector:@selector(setColor:)]];
3358 }
3359
3360 - (void)_alignSelectionUsingCSSValue:(NSString *)CSSAlignmentValue
3361 {
3362     if (![self _canEdit])
3363         return;
3364         
3365     // FIXME 3675191: This doesn't work yet. Maybe it's blocked by 3654850, or maybe something other than
3366     // just applyStyle: needs to be called for block-level attributes like this.
3367     DOMCSSStyleDeclaration *style = [self _emptyStyle];
3368     [style setTextAlign:CSSAlignmentValue];
3369     [self _applyStyleToSelection:style];
3370 }
3371
3372 - (void)alignCenter:(id)sender
3373 {
3374     [self _alignSelectionUsingCSSValue:@"center"];
3375 }
3376
3377 - (void)alignJustified:(id)sender
3378 {
3379     [self _alignSelectionUsingCSSValue:@"justify"];
3380 }
3381
3382 - (void)alignLeft:(id)sender
3383 {
3384     [self _alignSelectionUsingCSSValue:@"left"];
3385 }
3386
3387 - (void)alignRight:(id)sender
3388 {
3389     [self _alignSelectionUsingCSSValue:@"right"];
3390 }
3391
3392 - (void)insertTab:(id)sender
3393 {
3394     [self insertText:@"\t"];
3395 }
3396
3397 - (void)insertBacktab:(id)sender
3398 {
3399     // Doing nothing matches normal NSTextView behavior. If we ever use WebView for a field-editor-type purpose
3400     // we might add code here.
3401 }
3402
3403 - (void)insertNewline:(id)sender
3404 {
3405     if (![self _canEdit])
3406         return;
3407         
3408     // Perhaps we should make this delegate call sensitive to the real DOM operation we actually do.
3409     WebBridge *bridge = [self _bridge];
3410     if ([self _shouldReplaceSelectionWithText:@"\n" givenAction:WebViewInsertActionTyped]) {
3411         [bridge insertNewline];
3412     }
3413 }
3414
3415 - (void)insertParagraphSeparator:(id)sender
3416 {
3417     if (![self _canEdit])
3418         return;
3419
3420     // FIXME: Should this do something different? Do we have the equivalent of a paragraph separator?
3421     [self insertNewline:sender];
3422 }
3423
3424 - (void)_changeWordCaseWithSelector:(SEL)selector
3425 {
3426     if (![self _canEdit])
3427         return;
3428
3429     WebBridge *bridge = [self _bridge];
3430     [self selectWord:nil];
3431     NSString *word = [[bridge selectedString] performSelector:selector];
3432     // FIXME: Does this need a different action context other than "typed"?
3433     if ([self _shouldReplaceSelectionWithText:word givenAction:WebViewInsertActionTyped]) {
3434         [bridge replaceSelectionWithText:word selectReplacement:NO smartReplace:NO];
3435     }
3436 }
3437
3438 - (void)uppercaseWord:(id)sender
3439 {
3440     [self _changeWordCaseWithSelector:@selector(uppercaseString)];
3441 }
3442
3443 - (void)lowercaseWord:(id)sender
3444 {
3445     [self _changeWordCaseWithSelector:@selector(lowercaseString)];
3446 }
3447
3448 - (void)capitalizeWord:(id)sender
3449 {
3450     [self _changeWordCaseWithSelector:@selector(capitalizedString)];
3451 }
3452
3453 - (BOOL)_deleteWithDirection:(WebSelectionDirection)direction granularity:(WebSelectionGranularity)granularity killRing:(BOOL)killRing
3454 {
3455     // Delete the selection, if there is one.
3456     // If not, make a selection using the passed-in direction and granularity.
3457     if (![self _canEdit])
3458         return NO;
3459         
3460     DOMRange *range;
3461     BOOL prepend = NO;
3462     BOOL smartDeleteOK = NO;
3463     if ([self _hasSelection]) {
3464         range = [self _selectedRange];
3465         smartDeleteOK = YES;
3466     } else {
3467         WebBridge *bridge = [self _bridge];
3468         range = [bridge rangeByAlteringCurrentSelection:WebSelectByExtending direction:direction granularity:granularity];
3469         if (range == nil || [range collapsed])
3470             return NO;
3471         switch (direction) {
3472             case WebSelectForward:
3473             case WebSelectRight:
3474                 break;
3475             case WebSelectBackward:
3476             case WebSelectLeft:
3477                 prepend = YES;
3478                 break;
3479         }
3480     }
3481     [self _deleteRange:range preflight:YES killRing:killRing prepend:prepend smartDeleteOK:smartDeleteOK];
3482     return YES;
3483 }
3484
3485 - (void)deleteForward:(id)sender
3486 {
3487     [self _deleteWithDirection:WebSelectForward granularity:WebSelectByCharacter killRing:NO];
3488 }
3489
3490 - (void)deleteBackward:(id)sender
3491 {
3492     if (![self _isEditable])
3493         return;
3494     if ([self _hasSelection]) {
3495         [self _deleteSelection];
3496     } else {
3497         // FIXME: We are not calling the delegate here. Why can't we just call _deleteRange here?
3498         [[self _bridge] deleteKeyPressed];
3499     }
3500 }
3501
3502 - (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender
3503 {
3504     ERROR("unimplemented, doing deleteBackward instead");
3505     [self deleteBackward:sender];
3506 }
3507
3508 - (void)deleteWordForward:(id)sender
3509 {
3510     [self _deleteWithDirection:WebSelectForward granularity:WebSelectByWord killRing:YES];
3511 }
3512
3513 - (void)deleteWordBackward:(id)sender
3514 {
3515     [self _deleteWithDirection:WebSelectBackward granularity:WebSelectByWord killRing:YES];
3516 }
3517
3518 - (void)deleteToBeginningOfLine:(id)sender
3519 {
3520     [self _deleteWithDirection:WebSelectBackward granularity:WebSelectToLineBoundary killRing:YES];
3521 }
3522
3523 - (void)deleteToEndOfLine:(id)sender
3524 {
3525     // FIXME: To match NSTextView, this command should delete the newline at the end of
3526     // a paragraph if you are at the end of a paragraph (like deleteToEndOfParagraph does below).
3527     [self _deleteWithDirection:WebSelectForward granularity:WebSelectToLineBoundary killRing:YES];
3528 }
3529
3530 - (void)deleteToBeginningOfParagraph:(id)sender
3531 {
3532     [self _deleteWithDirection:WebSelectBackward granularity:WebSelectToParagraphBoundary killRing:YES];
3533 }
3534
3535 - (void)deleteToEndOfParagraph:(id)sender
3536 {
3537     // Despite the name of the method, this should delete the newline if the caret is at the end of a paragraph.
3538     // If deletion to the end of the paragraph fails, we delete one character forward, which will delete the newline.
3539     if (![self _deleteWithDirection:WebSelectForward granularity:WebSelectToParagraphBoundary killRing:YES])
3540         [self _deleteWithDirection:WebSelectForward granularity:WebSelectByCharacter killRing:YES];
3541 }
3542
3543 - (void)complete:(id)sender
3544 {
3545     if (![self _canEdit])
3546         return;
3547
3548     if (!_private->compController) {
3549         _private->compController = [[WebTextCompleteController alloc] initWithHTMLView:self];
3550     }
3551     [_private->compController doCompletion];
3552 }
3553
3554 - (void)checkSpelling:(id)sender
3555 {
3556     // WebCore does everything but update the spelling panel
3557     NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
3558     if (!checker) {
3559         ERROR("No NSSpellChecker");
3560         return;
3561     }
3562     NSString *badWord = [[self _bridge] advanceToNextMisspelling];
3563     if (badWord) {
3564         [checker updateSpellingPanelWithMisspelledWord:badWord];
3565     }
3566 }
3567
3568 - (void)showGuessPanel:(id)sender
3569 {
3570     // WebCore does everything but update the spelling panel
3571     NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
3572     if (!checker) {
3573         ERROR("No NSSpellChecker");
3574         return;
3575     }
3576     NSString *badWord = [[self _bridge] advanceToNextMisspellingStartingJustBeforeSelection];
3577     if (badWord) {
3578         [checker updateSpellingPanelWithMisspelledWord:badWord];
3579     }
3580     [[checker spellingPanel] orderFront:sender];
3581 }
3582
3583 - (void)_changeSpellingToWord:(NSString *)newWord
3584 {
3585     if (![self _canEdit])
3586         return;
3587
3588     // Don't correct to empty string.  (AppKit checked this, we might as well too.)
3589     if (![NSSpellChecker sharedSpellChecker]) {
3590         ERROR("No NSSpellChecker");
3591         return;
3592     }
3593     
3594     if ([newWord isEqualToString:@""]) {
3595         return;
3596     }
3597
3598     if ([self _shouldReplaceSelectionWithText:newWord givenAction:WebViewInsertActionPasted]) {
3599         [[self _bridge] replaceSelectionWithText:newWord selectReplacement:YES smartReplace:NO];
3600     }
3601 }
3602
3603 - (void)changeSpelling:(id)sender
3604 {
3605     [self _changeSpellingToWord:[[sender selectedCell] stringValue]];
3606 }
3607
3608 - (void)ignoreSpelling:(id)sender
3609 {
3610     if (![self _canEdit])
3611         return;
3612     
3613     NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
3614     if (!checker) {
3615         ERROR("No NSSpellChecker");
3616         return;
3617     }
3618     
3619     NSString *stringToIgnore = [sender stringValue];
3620     unsigned int length = [stringToIgnore length];
3621     if (stringToIgnore && length > 0) {
3622         [checker ignoreWord:stringToIgnore inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]];
3623         // FIXME: Need to clear misspelling marker if the currently selected word is the one we are to ignore?
3624     }
3625 }
3626
3627 - (void)performFindPanelAction:(id)sender
3628 {
3629     // Implementing this will probably require copying all of NSFindPanel.h and .m.
3630     // We need *almost* the same thing as AppKit, but not quite.
3631     ERROR("unimplemented");
3632 }
3633
3634 - (void)startSpeaking:(id)sender
3635 {
3636     WebBridge *bridge = [self _bridge];
3637     DOMRange *range = [self _selectedRange];
3638     if (!range || [range collapsed]) {
3639         range = [self _documentRange];
3640     }
3641     [NSApp speakString:[bridge stringForRange:range]];
3642 }
3643
3644 - (void)stopSpeaking:(id)sender
3645 {
3646     [NSApp stopSpeaking:sender];
3647 }
3648
3649 - (void)insertNewlineIgnoringFieldEditor:(id)sender
3650 {
3651     [self insertNewline:sender];
3652 }
3653
3654 - (void)insertTabIgnoringFieldEditor:(id)sender
3655 {
3656     [self insertTab:sender];
3657 }
3658
3659 - (void)subscript:(id)sender
3660 {
3661     DOMCSSStyleDeclaration *style = [self _emptyStyle];
3662     [style setVerticalAlign:@"sub"];
3663     [self _applyStyleToSelection:style];
3664 }
3665
3666 - (void)superscript:(id)sender
3667 {
3668     DOMCSSStyleDeclaration *style = [self _emptyStyle];
3669     [style setVerticalAlign:@"super"];
3670     [self _applyStyleToSelection:style];
3671 }
3672
3673 - (void)unscript:(id)sender
3674 {
3675     DOMCSSStyleDeclaration *style = [self _emptyStyle];
3676     [style setVerticalAlign:@"baseline"];
3677     [self _applyStyleToSelection:style];
3678 }
3679
3680 - (void)underline:(id)sender
3681 {
3682     // Despite the name, this method is actually supposed to toggle underline.
3683     // FIXME: This currently clears overline, line-through, and blink as an unwanted side effect.
3684     DOMCSSStyleDeclaration *style = [self _emptyStyle];
3685     [style setTextDecoration:@"underline"];
3686     if ([[self _bridge] selectionStartHasStyle:style])
3687         [style setTextDecoration:@"none"];
3688     [self _applyStyleToSelection:style];
3689 }
3690
3691 - (void)yank:(id)sender
3692 {
3693     if (![self _canEdit])
3694         return;
3695         
3696     [self insertText:_NSYankFromKillRing()];
3697     _NSSetKillRingToYankedState();
3698 }
3699
3700 - (void)yankAndSelect:(id)sender
3701 {
3702     if (![self _canEdit])
3703         return;
3704
3705     [self _insertText:_NSYankPreviousFromKillRing() selectInsertedText:YES];
3706     _NSSetKillRingToYankedState();
3707 }
3708
3709 - (void)setMark:(id)sender
3710 {
3711     [[self _bridge] setMarkDOMRange:[self _selectedRange]];
3712 }
3713
3714 static DOMRange *unionDOMRanges(DOMRange *a, DOMRange *b)
3715 {
3716     ASSERT(a);
3717     ASSERT(b);
3718     DOMRange *s = [a compareBoundaryPoints:DOM_START_TO_START :b] <= 0 ? a : b;
3719     DOMRange *e = [a compareBoundaryPoints:DOM_END_TO_END :b] <= 0 ? b : a;
3720     DOMRange *r = [[[a startContainer] ownerDocument] createRange];
3721     [r setStart:[s startContainer] :[s startOffset]];
3722     [r setEnd:[e endContainer] :[e endOffset]];
3723     return r;
3724 }
3725
3726 - (void)deleteToMark:(id)sender
3727 {
3728     if (![self _canEdit])
3729         return;
3730
3731     DOMRange *mark = [[self _bridge] markDOMRange];
3732     if (mark == nil) {
3733         [self delete:sender];
3734     } else {
3735         DOMRange *selection = [self _selectedRange];
3736         DOMRange *r;
3737         @try {
3738             r = unionDOMRanges(mark, selection);
3739         } @catch (NSException *exception) {
3740             r = selection;
3741         }
3742         [self _deleteRange:r preflight:YES killRing:YES prepend:YES smartDeleteOK:NO];
3743     }
3744     [self setMark:sender];
3745 }
3746
3747 - (void)selectToMark:(id)sender
3748 {
3749     WebBridge *bridge = [self _bridge];
3750     DOMRange *mark = [bridge markDOMRange];
3751     if (mark == nil) {
3752         NSBeep();
3753         return;
3754     }
3755     DOMRange *selection = [self _selectedRange];
3756     @try {
3757         [bridge setSelectedDOMRange:unionDOMRanges(mark, selection) affinity:NSSelectionAffinityUpstream];
3758     } @catch (NSException *exception) {
3759         NSBeep();
3760     }
3761 }
3762
3763 - (void)swapWithMark:(id)sender
3764 {
3765     if (![self _canEdit])
3766         return;
3767
3768     WebBridge *bridge = [self _bridge];
3769     DOMRange *mark = [bridge markDOMRange];
3770     if (mark == nil) {
3771         NSBeep();
3772         return;
3773     }
3774     DOMRange *selection = [self _selectedRange];
3775     @try {
3776         [bridge setSelectedDOMRange:mark affinity:NSSelectionAffinityUpstream];
3777     } @catch (NSException *exception) {
3778         NSBeep();
3779         return;
3780     }
3781     [bridge setMarkDOMRange:selection];
3782 }
3783
3784 - (void)transpose:(id)sender
3785 {
3786     if (![self _canEdit])
3787         return;
3788
3789     WebBridge *bridge = [self _bridge];
3790     DOMRange *r = [bridge rangeOfCharactersAroundCaret];
3791     if (!r) {
3792         return;
3793     }
3794     NSString *characters = [bridge stringForRange:r];
3795     if ([characters length] != 2) {
3796         return;
3797     }
3798     NSString *transposed = [[characters substringFromIndex:1] stringByAppendingString:[characters substringToIndex:1]];
3799     WebView *webView = [self _webView];
3800     if (![[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:r affinity:NSSelectionAffinityUpstream stillSelecting:NO]) {
3801         return;
3802     }
3803     [bridge setSelectedDOMRange:r affinity:NSSelectionAffinityUpstream];
3804     if ([self _shouldReplaceSelectionWithText:transposed givenAction:WebViewInsertActionTyped]) {
3805         [bridge replaceSelectionWithText:transposed selectReplacement:NO smartReplace:NO];
3806     }
3807 }
3808
3809 #if 0
3810
3811 // CSS does not have a way to specify an outline font, which may make this difficult to implement.
3812 // Maybe a special case of text-shadow?
3813 - (void)outline:(id)sender;
3814
3815 // This is part of table support, which may be in NSTextView for Tiger.
3816 // It's probably simple to do the equivalent thing for WebKit.
3817 - (void)insertTable:(id)sender;
3818
3819 // === key binding methods that NSTextView has that don't have standard key bindings
3820
3821 // These could be important.
3822 - (void)toggleBaseWritingDirection:(id)sender;
3823 - (void)toggleTraditionalCharacterShape:(id)sender;
3824 - (void)changeBaseWritingDirection:(id)sender;
3825
3826 // I'm not sure what the equivalents of these in the web world are; we use <br> as a paragraph break.
3827 - (void)insertLineBreak:(id)sender;
3828 - (void)insertLineSeparator:(id)sender;
3829 - (void)insertPageBreak:(id)sender;
3830
3831 // === methods not present in NSTextView
3832
3833 // These methods are not implemented in NSTextView yet, so perhaps there's no rush.
3834 - (void)changeCaseOfLetter:(id)sender;
3835 - (void)indent:(id)sender;
3836 - (void)transposeWords:(id)sender;
3837
3838 #endif
3839
3840 // Super-hack alert.
3841 // Workaround for bug 3789278.
3842
3843 // Returns a selector only if called while:
3844 //   1) first responder is self
3845 //   2) handling a key down event
3846 //   3) not yet inside keyDown: method
3847 //   4) key is an arrow key
3848 // The selector is the one that gets sent by -[NSWindow _processKeyboardUIKey] for this key.
3849 - (SEL)_arrowKeyDownEventSelectorIfPreprocessing
3850 {
3851     NSWindow *w = [self window];
3852     if ([w firstResponder] != self)
3853         return NULL;
3854     NSEvent *e = [w currentEvent];
3855     if ([e type] != NSKeyDown)
3856         return NULL;
3857     if (e == _private->keyDownEvent)
3858         return NULL;
3859     NSString *s = [e charactersIgnoringModifiers];
3860     if ([s length] == 0)
3861         return NULL;
3862     switch ([s characterAtIndex:0]) {
3863         case NSDownArrowFunctionKey:
3864             return @selector(moveDown:);
3865         case NSLeftArrowFunctionKey:
3866             return @selector(moveLeft:);
3867         case NSRightArrowFunctionKey:
3868             return @selector(moveRight:);
3869         case NSUpArrowFunctionKey:
3870             return @selector(moveUp:);
3871         default:
3872             return NULL;
3873     }
3874 }
3875
3876 // Returns NO instead of YES if called on the selector that the
3877 // _arrowKeyDownEventSelectorIfPreprocessing method returns.
3878 // This should only happen inside -[NSWindow _processKeyboardUIKey],
3879 // and together with the change below should cause that method
3880 // to return NO rather than handling the key.
3881 // Also set a 1-shot flag for the nextResponder check below.
3882 - (BOOL)respondsToSelector:(SEL)selector
3883 {
3884     if (![super respondsToSelector:selector])
3885         return NO;
3886     SEL arrowKeySelector = [self _arrowKeyDownEventSelectorIfPreprocessing];
3887     if (selector != arrowKeySelector)
3888         return YES;
3889     _private->nextResponderDisabledOnce = YES;
3890     return NO;
3891 }
3892
3893 // Returns nil instead of the next responder if called when the
3894 // one-shot flag is set, and _arrowKeyDownEventSelectorIfPreprocessing
3895 // returns something other than NULL. This should only happen inside
3896 // -[NSWindow _processKeyboardUIKey] and together with the change above
3897 // should cause that method to return NO rather than handling the key.
3898 - (NSResponder *)nextResponder
3899 {
3900     BOOL disabled = _private->nextResponderDisabledOnce;
3901     _private->nextResponderDisabledOnce = NO;
3902     if (disabled && [self _arrowKeyDownEventSelectorIfPreprocessing] != NULL) {
3903         return nil;
3904     }
3905     return [super nextResponder];
3906 }
3907
3908 @end
3909
3910 @implementation WebHTMLView (WebTextSizing)
3911
3912 - (void)_web_textSizeMultiplierChanged
3913 {
3914     [self _updateTextSizeMultiplier];
3915 }
3916
3917 @end
3918
3919 @implementation NSArray (WebHTMLView)
3920
3921 - (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object
3922 {
3923     NSEnumerator *enumerator = [self objectEnumerator];
3924     WebNetscapePluginEmbeddedView *view;
3925     while ((view = [enumerator nextObject]) != nil) {
3926         if ([view isKindOfClass:[WebNetscapePluginEmbeddedView class]]) {
3927             [view performSelector:selector withObject:object];
3928         }
3929     }
3930 }
3931
3932 @end
3933
3934 @implementation WebHTMLView (WebInternal)
3935
3936 - (void)_selectionChanged
3937 {
3938     [self _updateSelectionForInputManager];
3939     [self _updateFontPanel];
3940     _private->startNewKillRingSequence = YES;
3941 }
3942
3943 - (void)_updateFontPanel
3944 {
3945     // FIXME: NSTextView bails out if becoming or resigning first responder, for which it has ivar flags. Not
3946     // sure if we need to do something similar.
3947     
3948     if (![self _canEdit])
3949         return;
3950     
3951     NSWindow *window = [self window];
3952     // FIXME: is this first-responder check correct? What happens if a subframe is editable and is first responder?
3953     if ([NSApp keyWindow] != window || [window firstResponder] != self) {
3954         return;
3955     }
3956     
3957     BOOL multiple = NO;
3958     NSFont *font = [[self _bridge] fontForSelection:&multiple];
3959
3960     // FIXME: for now, return a bogus font that distinguishes the empty selection from the non-empty
3961     // selection. We should be able to remove this once the rest of this code works properly.
3962     if (font == nil) {
3963         if (![self _hasSelection]) {
3964             font = [NSFont toolTipsFontOfSize:17];
3965         } else {
3966             font = [NSFont menuFontOfSize:23];
3967         }
3968     }
3969     ASSERT(font != nil);
3970
3971     NSFontManager *fm = [NSFontManager sharedFontManager];
3972     [fm setSelectedFont:font isMultiple:multiple];
3973
3974     // FIXME: we don't keep track of selected attributes, or set them on the font panel. This
3975     // appears to have no effect on the UI. E.g., underlined text in Mail or TextEdit is
3976     // not reflected in the font panel. Maybe someday this will change.
3977 }
3978
3979 - (unsigned int)_delegateDragSourceActionMask
3980 {
3981     WebView *webView = [self _webView];
3982     NSPoint point = [webView convertPoint:[_private->mouseDownEvent locationInWindow] fromView:nil];
3983     _private->dragSourceActionMask = [[webView _UIDelegateForwarder] webView:webView dragSourceActionMaskForPoint:point];
3984     return _private->dragSourceActionMask;
3985 }
3986
3987 - (BOOL)_canSmartCopyOrDelete
3988 {
3989     return [[self _webView] smartInsertDeleteEnabled] && [[self _bridge] selectionGranularity] == WebSelectByWord;
3990 }
3991
3992 @end
3993
3994 @implementation WebHTMLView (WebNSTextInputSupport)
3995
3996 - (NSArray *)validAttributesForMarkedText
3997 {
3998     // FIXME: TEXTINPUT: validAttributesForMarkedText not yet implemented
3999     return [NSArray array];
4000 }
4001
4002 - (unsigned int)characterIndexForPoint:(NSPoint)thePoint
4003 {
4004     ERROR("TEXTINPUT: characterIndexForPoint: not yet implemented");
4005     return 0;
4006 }
4007
4008 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
4009 {
4010     ERROR("TEXTINPUT: firstRectForCharacterRange: not yet implemented");
4011     return NSMakeRect(0,0,0,0);
4012 }
4013
4014 - (NSRange)selectedRange
4015 {
4016     ERROR("TEXTINPUT: selectedRange not yet implemented");
4017     return NSMakeRange(0,0);
4018 }
4019
4020 - (NSRange)markedRange
4021 {
4022     if (![self hasMarkedText]) {
4023         return NSMakeRange(NSNotFound,0);
4024     }
4025
4026     DOMRange *markedTextDOMRange = [[self _bridge] markedTextDOMRange];
4027
4028     unsigned rangeLocation = [markedTextDOMRange startOffset];
4029     unsigned rangeLength = [markedTextDOMRange endOffset] - rangeLocation;
4030
4031     return NSMakeRange(rangeLocation, rangeLength);
4032 }
4033
4034 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange
4035 {
4036     ERROR("TEXTINPUT: attributedSubstringFromRange: not yet implemented");
4037     return nil;
4038 }
4039
4040 - (long)conversationIdentifier
4041 {
4042     return (long)self;
4043 }
4044
4045 - (BOOL)hasMarkedText
4046 {
4047     return [[self _bridge] markedTextDOMRange] != nil;
4048 }
4049
4050 - (void)unmarkText
4051 {
4052     [[self _bridge] setMarkedTextDOMRange:nil];
4053 }
4054
4055 - (void)_selectMarkedText
4056 {
4057     if ([self hasMarkedText]) {
4058         WebBridge *bridge = [self _bridge];
4059         DOMRange *markedTextRange = [bridge markedTextDOMRange];
4060         [bridge setSelectedDOMRange:markedTextRange affinity:NSSelectionAffinityUpstream];
4061     }
4062 }
4063
4064 - (void)_selectRangeInMarkedText:(NSRange)range
4065 {
4066     ASSERT([self hasMarkedText]);
4067
4068     WebBridge *bridge = [self _bridge];
4069     DOMRange *selectedRange = [[bridge DOMDocument] createRange];
4070     DOMRange *markedTextRange = [bridge markedTextDOMRange];
4071     
4072     ASSERT([markedTextRange startContainer] == [markedTextRange endContainer]);
4073
4074     unsigned selectionStart = [markedTextRange startOffset] + range.location;
4075     unsigned selectionEnd = selectionStart + range.length;
4076
4077     [selectedRange setStart:[markedTextRange startContainer] :selectionStart];
4078     [selectedRange setEnd:[markedTextRange startContainer] :selectionEnd];
4079
4080     [bridge setSelectedDOMRange:selectedRange affinity:NSSelectionAffinityUpstream];
4081 }
4082
4083 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
4084 {
4085     WebBridge *bridge = [self _bridge];
4086
4087     if (![self _isEditable])
4088         return;
4089
4090     _private->ignoreMarkedTextSelectionChange = YES;
4091
4092     // if we had marked text already, we need to make sure to replace
4093     // that, instead of the selection/caret
4094     [self _selectMarkedText];
4095
4096     NSString *text;
4097     if ([string isKindOfClass:[NSAttributedString class]]) {
4098         ERROR("TEXTINPUT: requested set marked text with attributed string");
4099         text = [string string];
4100     } else {
4101         text = string;
4102     }
4103
4104     [bridge replaceSelectionWithText:text selectReplacement:YES smartReplace:NO];
4105     [bridge setMarkedTextDOMRange:[self _selectedRange]];
4106     [self _selectRangeInMarkedText:newSelRange];
4107
4108     _private->ignoreMarkedTextSelectionChange = NO;
4109 }
4110
4111 - (void)doCommandBySelector:(SEL)aSelector
4112 {
4113     WebView *webView = [self _webView];
4114     // FIXME 3810158: need to enable this code when Mail returns NO for this delegate method
4115     if (![[webView _editingDelegateForwarder] webView:webView doCommandBySelector:aSelector] || YES) {
4116 //  if (![[webView _editingDelegateForwarder] webView:webView doCommandBySelector:aSelector]) {
4117         [super doCommandBySelector:aSelector];
4118     }
4119 }
4120
4121 - (void)_discardMarkedText
4122 {
4123     if (![self hasMarkedText])
4124         return;
4125
4126     _private->ignoreMarkedTextSelectionChange = YES;
4127
4128     [self _selectMarkedText];
4129     [[NSInputManager currentInputManager] markedTextAbandoned:self];
4130     [self unmarkText];
4131     // FIXME: Should we be calling the delegate here?
4132     [[self _bridge] deleteSelectionWithSmartDelete:NO];
4133
4134     _private->ignoreMarkedTextSelectionChange = NO;
4135 }
4136
4137 - (void)_insertText:(NSString *)text selectInsertedText:(BOOL)selectText
4138 {
4139     if (text == nil || (![self _isEditable] && ![self hasMarkedText])) {
4140         return;
4141     }
4142