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