2 * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #import <WebKit/WebHTMLView.h>
31 #import <ApplicationServices/ApplicationServices.h>
32 #import <WebKit/DOM.h>
33 #import <WebKit/DOMExtensions.h>
34 #import <WebKit/DOMPrivate.h>
35 #import <WebKit/WebArchive.h>
36 #import <WebKit/WebArchiver.h>
37 #import <WebKit/WebBaseNetscapePluginViewInternal.h>
38 #import <WebKit/WebFrameBridge.h>
39 #import <WebKit/WebClipView.h>
40 #import <WebKit/WebDataProtocol.h>
41 #import <WebKit/WebDataSourceInternal.h>
42 #import <WebKit/WebDefaultUIDelegate.h>
43 #import <WebKit/WebDocumentInternal.h>
44 #import <WebKit/WebDOMOperationsPrivate.h>
45 #import <WebKit/WebEditingDelegate.h>
46 #import <WebKit/WebElementDictionary.h>
47 #import <WebKit/WebFramePrivate.h>
48 #import <WebKit/WebFrameInternal.h>
49 #import <WebKit/WebFrameViewInternal.h>
50 #import <WebKit/WebHTMLViewInternal.h>
51 #import <WebKit/WebHTMLRepresentationPrivate.h>
52 #import <WebKit/WebKitLogging.h>
53 #import <WebKit/WebKitNSStringExtras.h>
54 #import <WebKit/WebNetscapePluginEmbeddedView.h>
55 #import <WebKit/WebNSAttributedStringExtras.h>
56 #import <WebKit/WebNSEventExtras.h>
57 #import <WebKit/WebNSFileManagerExtras.h>
58 #import <WebKit/WebNSImageExtras.h>
59 #import <WebKit/WebNSObjectExtras.h>
60 #import <WebKit/WebNSPasteboardExtras.h>
61 #import <WebKit/WebNSPrintOperationExtras.h>
62 #import <WebKit/WebNSURLExtras.h>
63 #import <WebKit/WebNSViewExtras.h>
64 #import <WebKit/WebPluginController.h>
65 #import <WebKit/WebPreferences.h>
66 #import <WebKit/WebPreferencesPrivate.h>
67 #import <WebKit/WebResourcePrivate.h>
68 #import <WebKit/WebStringTruncator.h>
69 #import <WebKit/WebUIDelegatePrivate.h>
70 #import <WebKit/WebViewInternal.h>
71 #import <WebKitSystemInterface.h>
72 #import <WebCore/WebCoreTextRenderer.h>
74 #import <AppKit/NSAccessibility.h>
76 // Included so usage of _NSSoftLinkingGetFrameworkFuncPtr will compile
77 #import <mach-o/dyld.h>
80 // need to declare this because AppKit does not make it available as API or SPI
81 extern NSString *NSMarkedClauseSegmentAttributeName;
82 extern NSString *NSTextInputReplacementRangeAttributeName;
84 // Kill ring calls. Would be better to use NSKillRing.h, but that's not available in SPI.
85 void _NSInitializeKillRing(void);
86 void _NSAppendToKillRing(NSString *);
87 void _NSPrependToKillRing(NSString *);
88 NSString *_NSYankFromKillRing(void);
89 NSString *_NSYankPreviousFromKillRing(void);
90 void _NSNewKillRingSequence(void);
91 void _NSSetKillRingToYankedState(void);
92 void _NSResetKillRingOperationFlag(void);
94 @interface NSView (AppKitSecretsIKnowAbout)
95 - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
96 - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
98 - (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants;
99 - (void)_propagateDirtyRectsToOpaqueAncestors;
102 @interface NSApplication (AppKitSecretsIKnowAbout)
103 - (void)speakString:(NSString *)string;
106 @interface NSWindow (AppKitSecretsIKnowAbout)
107 - (id)_newFirstResponderAfterResigning;
110 @interface NSAttributedString (AppKitSecretsIKnowAbout)
111 - (id)_initWithDOMRange:(DOMRange *)domRange;
112 - (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
115 @interface NSSpellChecker (CurrentlyPrivateForTextView)
116 - (void)learnWord:(NSString *)word;
119 // By imaging to a width a little wider than the available pixels,
120 // thin pages will be scaled down a little, matching the way they
121 // print in IE and Camino. This lets them use fewer sheets than they
122 // would otherwise, which is presumably why other browsers do this.
123 // Wide pages will be scaled down more than this.
124 #define PrintingMinimumShrinkFactor 1.25f
126 // This number determines how small we are willing to reduce the page content
127 // in order to accommodate the widest line. If the page would have to be
128 // reduced smaller to make the widest line fit, we just clip instead (this
129 // behavior matches MacIE and Mozilla, at least)
130 #define PrintingMaximumShrinkFactor 2.0f
132 // This number determines how short the last printed page of a multi-page print session
133 // can be before we try to shrink the scale in order to reduce the number of pages, and
134 // thus eliminate the orphan.
135 #define LastPrintedPageOrphanRatio 0.1f
137 // This number determines the amount the scale factor is adjusted to try to eliminate orphans.
138 // It has no direct mathematical relationship to LastPrintedPageOrphanRatio, due to variable
139 // numbers of pages, logic to avoid breaking elements, and CSS-supplied hard page breaks.
140 #define PrintingOrphanShrinkAdjustment 1.1f
142 #define AUTOSCROLL_INTERVAL 0.1f
144 #define DRAG_LABEL_BORDER_X 4.0f
145 #define DRAG_LABEL_BORDER_Y 2.0f
146 #define DRAG_LABEL_RADIUS 5.0f
147 #define DRAG_LABEL_BORDER_Y_OFFSET 2.0f
149 #define MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP 120.0f
150 #define MAX_DRAG_LABEL_WIDTH 320.0f
152 #define DRAG_LINK_LABEL_FONT_SIZE 11.0f
153 #define DRAG_LINK_URL_FONT_SIZE 10.0f
155 // Any non-zero value will do, but using something recognizable might help us debug some day.
156 #define TRACKING_RECT_TAG 0xBADFACE
158 // FIXME: This constant is copied from AppKit's _NXSmartPaste constant.
159 #define WebSmartPastePboardType @"NeXT smart paste pasteboard type"
161 #define STANDARD_WEIGHT 5
162 #define MIN_BOLD_WEIGHT 9
163 #define STANDARD_BOLD_WEIGHT 10
166 deleteSelectionAction,
168 forwardDeleteKeyAction
171 // if YES, do the standard NSView hit test (which can't give the right result when HTML overlaps a view)
172 static BOOL forceNSViewHitTest = NO;
174 // if YES, do the "top WebHTMLView" it test (which we'd like to do all the time but can't because of Java requirements [see bug 4349721])
175 static BOOL forceWebHTMLViewHitTest = NO;
177 // Used to avoid linking with ApplicationServices framework for _DCMDictionaryServiceWindowShow
178 void *_NSSoftLinkingGetFrameworkFuncPtr(NSString *inUmbrellaFrameworkName,
179 NSString *inFrameworkName,
180 const char *inFuncName,
181 const struct mach_header **ioCachedFrameworkImageHeaderPtr);
184 @interface WebHTMLView (WebTextSizing) <_WebDocumentTextSizing>
187 @interface WebHTMLView (WebHTMLViewFileInternal)
188 - (BOOL)_imageExistsAtPaths:(NSArray *)paths;
189 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText chosePlainText:(BOOL *)chosePlainText;
190 - (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard;
191 - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
192 - (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard;
193 - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
194 - (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
195 - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action;
196 - (float)_calculatePrintHeight;
197 - (void)_updateTextSizeMultiplier;
198 - (DOMRange *)_selectedRange;
199 - (BOOL)_shouldDeleteRange:(DOMRange *)range;
200 - (void)_deleteRange:(DOMRange *)range
201 killRing:(BOOL)killRing
202 prepend:(BOOL)prepend
203 smartDeleteOK:(BOOL)smartDeleteOK
204 deletionAction:(WebDeletionAction)deletionAction
205 granularity:(WebBridgeSelectionGranularity)granularity;
206 - (void)_deleteSelection;
207 - (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard;
208 - (NSView *)_hitViewForEvent:(NSEvent *)event;
209 - (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString;
210 - (DOMRange *)_documentRange;
211 - (WebFrameBridge *)_bridge;
212 - (void)_setMouseDownEvent:(NSEvent *)event;
215 @interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in a normal category and stop doing the forward declaration trick.
216 - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize;
219 @interface WebHTMLView (WebNSTextInputSupport) <NSTextInput>
220 - (void)_updateSelectionForInputManager;
221 - (void)_insertText:(NSString *)text selectInsertedText:(BOOL)selectText;
224 @interface WebHTMLView (WebEditingStyleSupport)
225 - (DOMCSSStyleDeclaration *)_emptyStyle;
226 - (NSString *)_colorAsString:(NSColor *)color;
229 @interface NSView (WebHTMLViewFileInternal)
230 - (void)_web_setPrintingModeRecursive;
231 - (void)_web_clearPrintingModeRecursive;
232 - (void)_web_layoutIfNeededRecursive;
233 - (void)_web_layoutIfNeededRecursive:(NSRect)rect testDirtyRect:(bool)testDirtyRect;
236 @interface NSMutableDictionary (WebHTMLViewFileInternal)
237 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key;
240 // Handles the complete: text command
241 @interface WebTextCompleteController : NSObject
245 NSWindow *_popupWindow;
246 NSTableView *_tableView;
247 NSArray *_completions;
248 NSString *_originalString;
251 - (id)initWithHTMLView:(WebHTMLView *)view;
252 - (void)doCompletion;
253 - (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft;
254 - (BOOL)filterKeyDown:(NSEvent *)event;
255 - (void)_reflectSelection;
258 @implementation WebHTMLViewPrivate
262 ASSERT(autoscrollTimer == nil);
263 ASSERT(autoscrollTriggerEvent == nil);
265 [mouseDownEvent release];
266 [draggingImageURL release];
267 [pluginController release];
269 [compController release];
270 [firstResponderTextViewAtMouseDownTime release];
271 [dataSource release];
272 [highlighters release];
279 @implementation WebHTMLView (WebHTMLViewFileInternal)
281 - (DOMRange *)_documentRange
283 return [[[self _bridge] DOMDocument] _documentRange];
286 - (BOOL)_imageExistsAtPaths:(NSArray *)paths
288 NSArray *imageMIMETypes = [WebFrameBridge supportedImageResourceMIMETypes];
289 NSEnumerator *enumerator = [paths objectEnumerator];
292 while ((path = [enumerator nextObject]) != nil) {
293 NSString *MIMEType = WKGetMIMETypeForExtension([path pathExtension]);
294 if ([imageMIMETypes containsObject:MIMEType]) {
302 - (WebDataSource *)_dataSource
304 return _private->dataSource;
307 - (WebFrameBridge *)_bridge
309 return [_private->dataSource _bridge];
312 - (WebView *)_webView
314 return [_private->dataSource _webView];
317 - (WebFrameView *)_frameView
319 return [[_private->dataSource webFrame] frameView];
322 - (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths
324 DOMDocumentFragment *fragment;
325 NSArray *imageMIMETypes = [WebFrameBridge supportedImageResourceMIMETypes];
326 NSEnumerator *enumerator = [paths objectEnumerator];
327 WebDataSource *dataSource = [self _dataSource];
328 NSMutableArray *domNodes = [[NSMutableArray alloc] init];
331 while ((path = [enumerator nextObject]) != nil) {
332 NSString *MIMEType = WKGetMIMETypeForExtension([path pathExtension]);
333 if ([imageMIMETypes containsObject:MIMEType]) {
334 WebResource *resource = [[WebResource alloc] initWithData:[NSData dataWithContentsOfFile:path]
335 URL:[NSURL fileURLWithPath:path]
340 [domNodes addObject:[dataSource _imageElementWithImageResource:resource]];
344 // Non-image file types
345 NSString *url = [[[NSURL fileURLWithPath:path] _webkit_canonicalize] _web_userVisibleString];
346 [domNodes addObject:[[[self _bridge] DOMDocument] createTextNode: url]];
350 fragment = [[self _bridge] documentFragmentWithNodesAsParagraphs:domNodes];
354 return [fragment firstChild] != nil ? fragment : nil;
357 + (NSArray *)_excludedElementsForAttributedStringConversion
359 static NSArray *elements = nil;
360 if (elements == nil) {
361 elements = [[NSArray alloc] initWithObjects:
362 // Omit style since we want style to be inline so the fragment can be easily inserted.
364 // Omit xml so the result is not XHTML.
366 // Omit tags that will get stripped when converted to a fragment anyway.
367 @"doctype", @"html", @"head", @"body",
368 // Omit deprecated tags.
369 @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u",
370 // Omit object so no file attachments are part of the fragment.
376 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard
377 inContext:(DOMRange *)context
378 allowPlainText:(BOOL)allowPlainText
379 chosePlainText:(BOOL *)chosePlainText
381 NSArray *types = [pasteboard types];
382 *chosePlainText = NO;
384 if ([types containsObject:WebArchivePboardType]) {
385 WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]];
387 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithArchive:archive];
395 if ([types containsObject:NSFilenamesPboardType]) {
396 DOMDocumentFragment *fragment = [self _documentFragmentWithPaths:[pasteboard propertyListForType:NSFilenamesPboardType]];
397 if (fragment != nil) {
404 if ([types containsObject:NSHTMLPboardType]) {
405 NSString *HTMLString = [pasteboard stringForType:NSHTMLPboardType];
406 // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785.
407 if ([HTMLString hasPrefix:@"Version:"]) {
408 NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch];
409 if (range.location != NSNotFound) {
410 HTMLString = [HTMLString substringFromIndex:range.location];
413 if ([HTMLString length] != 0) {
414 return [[self _bridge] documentFragmentWithMarkupString:HTMLString baseURLString:nil];
418 NSAttributedString *string = nil;
419 if ([types containsObject:NSRTFDPboardType]) {
420 string = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
422 if (string == nil && [types containsObject:NSRTFPboardType]) {
423 string = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
426 NSDictionary *documentAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
427 [[self class] _excludedElementsForAttributedStringConversion], NSExcludedElementsDocumentAttribute,
428 self, @"WebResourceHandler", nil];
429 NSArray *subresources;
430 DOMDocumentFragment *fragment = [string _documentFromRange:NSMakeRange(0, [string length])
431 document:[[self _bridge] DOMDocument]
432 documentAttributes:documentAttributes
433 subresources:&subresources];
434 [documentAttributes release];
439 if ([types containsObject:NSTIFFPboardType]) {
440 WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSTIFFPboardType]
441 URL:[NSURL _web_uniqueWebDataURLWithRelativeString:@"/image.tiff"]
442 MIMEType:@"image/tiff"
445 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
450 if ([types containsObject:NSPICTPboardType]) {
451 WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPICTPboardType]
452 URL:[NSURL _web_uniqueWebDataURLWithRelativeString:@"/image.pict"]
453 MIMEType:@"image/pict"
456 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
461 if ((URL = [NSURL URLFromPasteboard:pasteboard])) {
462 DOMDocument* document = [[self _bridge] DOMDocument];
465 DOMHTMLAnchorElement* anchor = (DOMHTMLAnchorElement*)[document createElement:@"a"];
466 NSString *URLString = [URL _web_userVisibleString];
467 NSString *URLTitleString = [pasteboard stringForType:WebURLNamePboardType];
468 DOMText* text = [document createTextNode:URLTitleString];
469 [anchor setHref:URLString];
470 [anchor appendChild:text];
471 DOMDocumentFragment* fragment = [document createDocumentFragment];
472 [fragment appendChild:anchor];
473 if ([URLString length] > 0)
478 if (allowPlainText && [types containsObject:NSStringPboardType]) {
479 *chosePlainText = YES;
480 return [[self _bridge] documentFragmentWithText:[pasteboard stringForType:NSStringPboardType]
487 - (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard
489 NSArray *types = [pasteboard types];
491 if ([types containsObject:NSStringPboardType])
492 return [pasteboard stringForType:NSStringPboardType];
494 NSAttributedString *attributedString = nil;
497 if ([types containsObject:NSRTFDPboardType])
498 attributedString = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
499 if (attributedString == nil && [types containsObject:NSRTFPboardType])
500 attributedString = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
501 if (attributedString != nil) {
502 string = [[attributedString string] copy];
503 [attributedString release];
504 return [string autorelease];
507 if ([types containsObject:NSFilenamesPboardType]) {
508 string = [[pasteboard propertyListForType:NSFilenamesPboardType] componentsJoinedByString:@"\n"];
515 if ((URL = [NSURL URLFromPasteboard:pasteboard])) {
516 string = [URL _web_userVisibleString];
517 if ([string length] > 0)
524 - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText
526 DOMRange *range = [self _selectedRange];
528 DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard
529 inContext:range allowPlainText:allowPlainText chosePlainText:&chosePlainText];
530 WebFrameBridge *bridge = [self _bridge];
531 if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[self _selectedRange] givenAction:WebViewInsertActionPasted]) {
532 [bridge replaceSelectionWithFragment:fragment selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard] matchStyle:chosePlainText];
536 - (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard
538 NSString *text = [self _plainTextFromPasteboard:pasteboard];
539 if ([self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionPasted])
540 [[self _bridge] replaceSelectionWithText:text selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]];
543 - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
545 WebView *webView = [self _webView];
546 DOMNode *child = [fragment firstChild];
547 if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]])
548 return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action];
549 return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action];
552 - (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
554 WebView *webView = [self _webView];
555 return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:range givenAction:action];
558 - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action
560 return [self _shouldInsertText:text replacingDOMRange:[self _selectedRange] givenAction:action];
563 // Calculate the vertical size of the view that fits on a single page
564 - (float)_calculatePrintHeight
566 // Obtain the print info object for the current operation
567 NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo];
569 // Calculate the page height in points
570 NSSize paperSize = [pi paperSize];
571 return paperSize.height - [pi topMargin] - [pi bottomMargin];
574 - (void)_updateTextSizeMultiplier
576 [[self _bridge] setTextSizeMultiplier:[[self _webView] textSizeMultiplier]];
579 - (DOMRange *)_selectedRange
581 return [[self _bridge] selectedDOMRange];
584 - (BOOL)_shouldDeleteRange:(DOMRange *)range
586 if (range == nil || [range collapsed])
589 if (![[self _bridge] canDeleteRange:range])
592 WebView *webView = [self _webView];
593 return [[webView _editingDelegateForwarder] webView:webView shouldDeleteDOMRange:range];
596 - (void)_deleteRange:(DOMRange *)range
597 killRing:(BOOL)killRing
598 prepend:(BOOL)prepend
599 smartDeleteOK:(BOOL)smartDeleteOK
600 deletionAction:(WebDeletionAction)deletionAction
601 granularity:(WebBridgeSelectionGranularity)granularity
603 WebFrameBridge *bridge = [self _bridge];
604 BOOL smartDelete = smartDeleteOK ? [self _canSmartCopyOrDelete] : NO;
606 BOOL startNewKillRingSequence = _private->startNewKillRingSequence;
609 if (startNewKillRingSequence) {
610 _NSNewKillRingSequence();
612 NSString *string = [bridge stringForRange:range];
614 _NSPrependToKillRing(string);
616 _NSAppendToKillRing(string);
618 startNewKillRingSequence = NO;
621 switch (deletionAction) {
622 case deleteSelectionAction:
623 [bridge setSelectedDOMRange:range affinity:NSSelectionAffinityDownstream closeTyping:YES];
624 [bridge deleteSelectionWithSmartDelete:smartDelete];
626 case deleteKeyAction:
627 [bridge setSelectedDOMRange:range affinity:NSSelectionAffinityDownstream closeTyping:(granularity != WebBridgeSelectByCharacter)];
628 [bridge deleteKeyPressedWithSmartDelete:smartDelete granularity:granularity];
630 case forwardDeleteKeyAction:
631 [bridge setSelectedDOMRange:range affinity:NSSelectionAffinityDownstream closeTyping:(granularity != WebBridgeSelectByCharacter)];
632 [bridge forwardDeleteKeyPressedWithSmartDelete:smartDelete granularity:granularity];
636 _private->startNewKillRingSequence = startNewKillRingSequence;
639 - (void)_deleteSelection
641 [self _deleteRange:[self _selectedRange]
645 deletionAction:deleteSelectionAction
646 granularity:WebBridgeSelectByCharacter];
649 - (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard
651 return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType];
654 - (NSView *)_hitViewForEvent:(NSEvent *)event
656 // Usually, we hack AK's hitTest method to catch all events at the topmost WebHTMLView.
657 // Callers of this method, however, want to query the deepest view instead.
658 forceNSViewHitTest = YES;
659 NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]];
660 forceNSViewHitTest = NO;
664 - (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString
666 // Put HTML on the pasteboard.
667 if ([types containsObject:WebArchivePboardType]) {
668 WebArchive *archive = [WebArchiver archiveSelectionInFrame:[self _frame]];
669 [pasteboard setData:[archive data] forType:WebArchivePboardType];
672 // Put the attributed string on the pasteboard (RTF/RTFD format).
673 if ([types containsObject:NSRTFDPboardType]) {
674 if (attributedString == nil) {
675 attributedString = [self selectedAttributedString];
677 NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
678 [pasteboard setData:RTFDData forType:NSRTFDPboardType];
680 if ([types containsObject:NSRTFPboardType]) {
681 if (attributedString == nil) {
682 attributedString = [self selectedAttributedString];
684 if ([attributedString containsAttachments]) {
685 attributedString = [attributedString _web_attributedStringByStrippingAttachmentCharacters];
687 NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
688 [pasteboard setData:RTFData forType:NSRTFPboardType];
691 // Put plain string on the pasteboard.
692 if ([types containsObject:NSStringPboardType]) {
693 // Map to a plain old space because this is better for source code, other browsers do it,
694 // and because HTML forces you to do this any time you want two spaces in a row.
695 NSMutableString *s = [[self selectedString] mutableCopy];
696 const unichar NonBreakingSpaceCharacter = 0xA0;
697 NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&NonBreakingSpaceCharacter length:1];
698 [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])];
699 [pasteboard setString:s forType:NSStringPboardType];
703 if ([self _canSmartCopyOrDelete] && [types containsObject:WebSmartPastePboardType]) {
704 [pasteboard setData:nil forType:WebSmartPastePboardType];
708 - (void)_setMouseDownEvent:(NSEvent *)event
710 ASSERT(!event || [event type] == NSLeftMouseDown || [event type] == NSRightMouseDown || [event type] == NSOtherMouseDown);
712 if (event == _private->mouseDownEvent)
716 [_private->mouseDownEvent release];
717 _private->mouseDownEvent = event;
719 [_private->firstResponderTextViewAtMouseDownTime release];
721 // The only code that checks this ivar only cares about NSTextViews. The code used to be more general,
722 // but it caused reference cycles leading to world leaks (see 4557386). We should be able to eliminate
723 // firstResponderTextViewAtMouseDownTime entirely when all the form controls are native widgets, because
724 // the only caller (in WebCore) will be unnecessary.
726 NSResponder *firstResponder = [[self window] firstResponder];
727 if ([firstResponder isKindOfClass:[NSTextView class]])
728 _private->firstResponderTextViewAtMouseDownTime = [firstResponder retain];
730 _private->firstResponderTextViewAtMouseDownTime = nil;
732 _private->firstResponderTextViewAtMouseDownTime = nil;
737 @implementation WebHTMLView (WebPrivate)
739 + (NSArray *)supportedMIMETypes
741 return [WebHTMLRepresentation supportedMIMETypes];
744 + (NSArray *)supportedImageMIMETypes
746 return [WebHTMLRepresentation supportedImageMIMETypes];
749 + (NSArray *)supportedNonImageMIMETypes
751 return [WebHTMLRepresentation supportedNonImageMIMETypes];
754 + (NSArray *)unsupportedTextMIMETypes
756 return [NSArray arrayWithObjects:
757 @"text/calendar", // iCal
761 @"text/vcard", // vCard
764 @"text/ldif", // Netscape Address Book
765 @"text/qif", // Quicken
767 @"text/x-csv", // CSV (for Address Book and Microsoft Outlook)
768 @"text/x-vcf", // vCard type used in Sun affinity app
769 @"text/rtf", // Rich Text Format
773 + (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent
775 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
776 location:[[flagsChangedEvent window] convertScreenToBase:[NSEvent mouseLocation]]
777 modifierFlags:[flagsChangedEvent modifierFlags]
778 timestamp:[flagsChangedEvent timestamp]
779 windowNumber:[flagsChangedEvent windowNumber]
780 context:[flagsChangedEvent context]
781 eventNumber:0 clickCount:0 pressure:0];
783 // Pretend it's a mouse move.
784 [[NSNotificationCenter defaultCenter]
785 postNotificationName:WKMouseMovedNotification() object:self
786 userInfo:[NSDictionary dictionaryWithObject:fakeEvent forKey:@"NSEvent"]];
789 - (void)_updateMouseoverWithFakeEvent
791 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
792 location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
793 modifierFlags:[[NSApp currentEvent] modifierFlags]
794 timestamp:[NSDate timeIntervalSinceReferenceDate]
795 windowNumber:[[self window] windowNumber]
796 context:[[NSApp currentEvent] context]
797 eventNumber:0 clickCount:0 pressure:0];
799 [self _updateMouseoverWithEvent:fakeEvent];
802 - (void)_frameOrBoundsChanged
804 if (!NSEqualSizes(_private->lastLayoutSize, [(NSClipView *)[self superview] documentVisibleRect].size)) {
805 [self setNeedsLayout:YES];
806 [self setNeedsDisplay:YES];
807 [_private->compController endRevertingChange:NO moveLeft:NO];
810 NSPoint origin = [[self superview] bounds].origin;
811 if (!NSEqualPoints(_private->lastScrollPosition, origin)) {
812 [[self _bridge] sendScrollEvent];
813 [_private->compController endRevertingChange:NO moveLeft:NO];
815 WebView *webView = [self _webView];
816 [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[self _frameView]];
818 _private->lastScrollPosition = origin;
820 SEL selector = @selector(_updateMouseoverWithFakeEvent);
821 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector object:nil];
822 [self performSelector:selector withObject:nil afterDelay:0];
825 - (void)_setAsideSubviews
827 ASSERT(!_private->subviewsSetAside);
828 ASSERT(_private->savedSubviews == nil);
829 _private->savedSubviews = _subviews;
831 _private->subviewsSetAside = YES;
834 - (void)_restoreSubviews
836 ASSERT(_private->subviewsSetAside);
837 ASSERT(_subviews == nil);
838 _subviews = _private->savedSubviews;
839 _private->savedSubviews = nil;
840 _private->subviewsSetAside = NO;
843 // This is called when we are about to draw, but before our dirty rect is propagated to our ancestors.
844 // That's the perfect time to do a layout, except that ideally we'd want to be sure that we're dirty
845 // before doing it. As a compromise, when we're opaque we do the layout only when actually asked to
846 // draw, but when we're transparent we do the layout at this stage so views behind us know that they
847 // need to be redrawn (in case the layout causes some things to get dirtied).
848 - (void)_propagateDirtyRectsToOpaqueAncestors
850 if (![[self _webView] drawsBackground]) {
851 [self _web_layoutIfNeededRecursive];
853 [super _propagateDirtyRectsToOpaqueAncestors];
856 // Don't let AppKit even draw subviews. We take care of that.
857 - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView
859 // This helps when we print as part of a larger print process.
860 // If the WebHTMLView itself is what we're printing, then we will never have to do this.
861 BOOL wasInPrintingMode = _private->printing;
862 BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
863 if (wasInPrintingMode != isPrinting) {
865 [self _web_setPrintingModeRecursive];
867 [self _web_clearPrintingModeRecursive];
871 [self _web_layoutIfNeededRecursive: rect testDirtyRect:YES];
872 [_subviews makeObjectsPerformSelector:@selector(_propagateDirtyRectsToOpaqueAncestors)];
874 [self _setAsideSubviews];
875 [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect
876 rectIsVisibleRectForView:visibleView topView:topView];
877 [self _restoreSubviews];
879 if (wasInPrintingMode != isPrinting) {
880 if (wasInPrintingMode) {
881 [self _web_setPrintingModeRecursive];
883 [self _web_clearPrintingModeRecursive];
888 // Don't let AppKit even draw subviews. We take care of that.
889 - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect
891 BOOL needToSetAsideSubviews = !_private->subviewsSetAside;
893 BOOL wasInPrintingMode = _private->printing;
894 BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
896 if (needToSetAsideSubviews) {
897 // This helps when we print as part of a larger print process.
898 // If the WebHTMLView itself is what we're printing, then we will never have to do this.
899 if (wasInPrintingMode != isPrinting) {
901 [self _web_setPrintingModeRecursive];
903 [self _web_clearPrintingModeRecursive];
907 NSRect boundsBeforeLayout = [self bounds];
908 [self _web_layoutIfNeededRecursive: visRect testDirtyRect:NO];
910 // If layout changes the view's bounds, then we need to recompute the visRect.
911 // That's because the visRect passed to us was based on the bounds at the time
912 // we were called. This method is only displayed to draw "all", so it's safe
913 // to just call visibleRect to compute the entire rectangle.
914 if (!NSEqualRects(boundsBeforeLayout, [self bounds])) {
915 visRect = [self visibleRect];
918 [self _setAsideSubviews];
921 [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect];
923 if (needToSetAsideSubviews) {
924 if (wasInPrintingMode != isPrinting) {
925 if (wasInPrintingMode) {
926 [self _web_setPrintingModeRecursive];
928 [self _web_clearPrintingModeRecursive];
932 [self _restoreSubviews];
936 - (WebHTMLView *)_topHTMLView
938 WebHTMLView *view = (WebHTMLView *)[[[[_private->dataSource _webView] mainFrame] frameView] documentView];
939 ASSERT([view isKindOfClass:[WebHTMLView class]]);
943 - (BOOL)_isTopHTMLView
945 return self == [self _topHTMLView];
948 - (BOOL)_insideAnotherHTMLView
950 return self != [self _topHTMLView];
953 - (void)scrollPoint:(NSPoint)point
955 // Since we can't subclass NSTextView to do what we want, we have to second guess it here.
956 // If we get called during the handling of a key down event, we assume the call came from
957 // NSTextView, and ignore it and use our own code to decide how to page up and page down
958 // We are smarter about how far to scroll, and we have "superview scrolling" logic.
959 NSEvent *event = [[self window] currentEvent];
960 if ([event type] == NSKeyDown) {
961 const unichar pageUp = NSPageUpFunctionKey;
962 if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageUp length:1]].length == 1) {
963 [self tryToPerform:@selector(scrollPageUp:) with:nil];
966 const unichar pageDown = NSPageDownFunctionKey;
967 if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageDown length:1]].length == 1) {
968 [self tryToPerform:@selector(scrollPageDown:) with:nil];
973 [super scrollPoint:point];
976 - (NSView *)hitTest:(NSPoint)point
978 // WebHTMLView objects handle all events for objects inside them.
979 // To get those events, we prevent hit testing from AppKit.
981 // But there are three exceptions to this:
982 // 1) For right mouse clicks and control clicks we don't yet have an implementation
983 // that works for nested views, so we let the hit testing go through the
984 // standard NSView code path (needs to be fixed, see bug 4361618).
985 // 2) Java depends on doing a hit test inside it's mouse moved handling,
986 // so we let the hit testing go through the standard NSView code path
987 // when the current event is a mouse move (except when we are calling
988 // from _updateMouseoverWithEvent, so we have to use a global,
989 // forceWebHTMLViewHitTest, for that)
990 // 3) The acceptsFirstMouse: and shouldDelayWindowOrderingForEvent: methods
991 // both need to figure out which view to check with inside the WebHTMLView.
992 // They use a global to change the behavior of hitTest: so they can get the
993 // right view. The global is forceNSViewHitTest and the method they use to
994 // do the hit testing is _hitViewForEvent:. (But this does not work correctly
995 // when there is HTML overlapping the view, see bug 4361626)
996 // 4) NSAccessibilityHitTest relies on this for checking the cursor position.
997 // Our check for that is whether the event is NSFlagsChanged. This works
998 // for VoiceOver's cntl-opt-f5 command (move focus to item under cursor)
999 // and Dictionary's cmd-cntl-D (open dictionary popup for item under cursor).
1000 // This is of course a hack.
1002 BOOL captureHitsOnSubviews;
1003 if (forceNSViewHitTest)
1004 captureHitsOnSubviews = NO;
1005 else if (forceWebHTMLViewHitTest)
1006 captureHitsOnSubviews = YES;
1008 NSEvent *event = [[self window] currentEvent];
1009 captureHitsOnSubviews = !([event type] == NSMouseMoved
1010 || [event type] == NSRightMouseDown
1011 || ([event type] == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask) != 0)
1012 || [event type] == NSFlagsChanged);
1015 if (!captureHitsOnSubviews)
1016 return [super hitTest:point];
1017 if ([[self superview] mouse:point inRect:[self frame]])
1022 static WebHTMLView *lastHitView = nil;
1024 - (void)_clearLastHitViewIfSelf
1026 if (lastHitView == self) {
1031 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside
1033 ASSERT(_private->trackingRectOwner == nil);
1034 _private->trackingRectOwner = owner;
1035 _private->trackingRectUserData = data;
1036 return TRACKING_RECT_TAG;
1039 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag
1041 ASSERT(tag == 0 || tag == TRACKING_RECT_TAG);
1042 ASSERT(_private->trackingRectOwner == nil);
1043 _private->trackingRectOwner = owner;
1044 _private->trackingRectUserData = data;
1045 return TRACKING_RECT_TAG;
1048 - (void)_addTrackingRects:(NSRect *)rects owner:(id)owner userDataList:(void **)userDataList assumeInsideList:(BOOL *)assumeInsideList trackingNums:(NSTrackingRectTag *)trackingNums count:(int)count
1051 ASSERT(trackingNums[0] == 0 || trackingNums[0] == TRACKING_RECT_TAG);
1052 ASSERT(_private->trackingRectOwner == nil);
1053 _private->trackingRectOwner = owner;
1054 _private->trackingRectUserData = userDataList[0];
1055 trackingNums[0] = TRACKING_RECT_TAG;
1058 - (void)removeTrackingRect:(NSTrackingRectTag)tag
1063 if (_private && (tag == TRACKING_RECT_TAG)) {
1064 _private->trackingRectOwner = nil;
1068 if (_private && (tag == _private->lastToolTipTag)) {
1069 [super removeTrackingRect:tag];
1070 _private->lastToolTipTag = 0;
1074 // If any other tracking rect is being removed, we don't know how it was created
1075 // and it's possible there's a leak involved (see 3500217)
1076 ASSERT_NOT_REACHED();
1079 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count
1082 for (i = 0; i < count; ++i) {
1086 ASSERT(tag == TRACKING_RECT_TAG);
1087 if (_private != nil) {
1088 _private->trackingRectOwner = nil;
1093 - (void)_sendToolTipMouseExited
1095 // Nothing matters except window, trackingNumber, and userData.
1096 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
1097 location:NSMakePoint(0, 0)
1100 windowNumber:[[self window] windowNumber]
1103 trackingNumber:TRACKING_RECT_TAG
1104 userData:_private->trackingRectUserData];
1105 [_private->trackingRectOwner mouseExited:fakeEvent];
1108 - (void)_sendToolTipMouseEntered
1110 // Nothing matters except window, trackingNumber, and userData.
1111 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
1112 location:NSMakePoint(0, 0)
1115 windowNumber:[[self window] windowNumber]
1118 trackingNumber:TRACKING_RECT_TAG
1119 userData:_private->trackingRectUserData];
1120 [_private->trackingRectOwner mouseEntered:fakeEvent];
1123 - (void)_setToolTip:(NSString *)string
1125 NSString *toolTip = [string length] == 0 ? nil : string;
1126 NSString *oldToolTip = _private->toolTip;
1127 if ((toolTip == nil || oldToolTip == nil) ? toolTip == oldToolTip : [toolTip isEqualToString:oldToolTip]) {
1131 [self _sendToolTipMouseExited];
1132 [oldToolTip release];
1134 _private->toolTip = [toolTip copy];
1136 // See radar 3500217 for why we remove all tooltips rather than just the single one we created.
1137 [self removeAllToolTips];
1138 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
1139 _private->lastToolTipTag = [self addToolTipRect:wideOpenRect owner:self userData:NULL];
1140 [self _sendToolTipMouseEntered];
1144 - (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
1146 return [[_private->toolTip copy] autorelease];
1149 - (void)_updateMouseoverWithEvent:(NSEvent *)event
1151 if (_private->closed)
1154 NSView *contentView = [[event window] contentView];
1155 NSPoint locationForHitTest = [[contentView superview] convertPoint:[event locationInWindow] fromView:nil];
1157 forceWebHTMLViewHitTest = YES;
1158 NSView *hitView = [contentView hitTest:locationForHitTest];
1159 forceWebHTMLViewHitTest = NO;
1161 WebHTMLView *view = nil;
1162 if ([hitView isKindOfClass:[WebHTMLView class]])
1163 view = (WebHTMLView *)hitView;
1168 if (lastHitView != view && lastHitView != nil) {
1169 // If we are moving out of a view (or frame), let's pretend the mouse moved
1170 // all the way out of that view. But we have to account for scrolling, because
1171 // khtml doesn't understand our clipping.
1172 NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect];
1173 float yScroll = visibleRect.origin.y;
1174 float xScroll = visibleRect.origin.x;
1176 event = [NSEvent mouseEventWithType:NSMouseMoved
1177 location:NSMakePoint(-1 - xScroll, -1 - yScroll )
1178 modifierFlags:[[NSApp currentEvent] modifierFlags]
1179 timestamp:[NSDate timeIntervalSinceReferenceDate]
1180 windowNumber:[[view window] windowNumber]
1181 context:[[NSApp currentEvent] context]
1182 eventNumber:0 clickCount:0 pressure:0];
1183 [[lastHitView _bridge] mouseMoved:event];
1189 [[view _bridge] mouseMoved:event];
1191 NSPoint point = [view convertPoint:[event locationInWindow] fromView:nil];
1192 NSDictionary *element = [view elementAtPoint:point];
1194 // Have the web view send a message to the delegate so it can do status bar display.
1195 [[view _webView] _mouseDidMoveOverElement:element modifierFlags:[event modifierFlags]];
1197 // Set a tool tip; it won't show up right away but will if the user pauses.
1198 NSString *newToolTip = nil;
1199 if (_private->showsURLsInToolTips) {
1200 DOMHTMLElement *domElement = [element objectForKey:WebElementDOMNodeKey];
1201 if ([domElement isKindOfClass:[DOMHTMLInputElement class]]) {
1202 if ([[(DOMHTMLInputElement *)domElement type] isEqualToString:@"submit"])
1203 newToolTip = [[(DOMHTMLInputElement *) domElement form] action];
1205 if (newToolTip == nil)
1206 newToolTip = [[element objectForKey:WebElementLinkURLKey] _web_userVisibleString];
1208 if (newToolTip == nil)
1209 newToolTip = [element objectForKey:WebElementTitleKey];
1210 [view _setToolTip:newToolTip];
1216 + (NSArray *)_insertablePasteboardTypes
1218 static NSArray *types = nil;
1220 types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType,
1221 NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSURLPboardType,
1222 NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, NSColorPboardType, nil];
1227 + (NSArray *)_selectionPasteboardTypes
1229 // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423).
1230 return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
1233 - (NSImage *)_dragImageForLinkElement:(NSDictionary *)element
1235 NSURL *linkURL = [element objectForKey: WebElementLinkURLKey];
1237 BOOL drawURLString = YES;
1238 BOOL clipURLString = NO, clipLabelString = NO;
1240 NSString *label = [element objectForKey: WebElementLinkLabelKey];
1241 NSString *urlString = [linkURL _web_userVisibleString];
1248 NSFont *labelFont = [[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:DRAG_LINK_LABEL_FONT_SIZE]
1249 toHaveTrait:NSBoldFontMask];
1250 NSFont *urlFont = [NSFont systemFontOfSize: DRAG_LINK_URL_FONT_SIZE];
1252 labelSize.width = [label _web_widthWithFont: labelFont];
1253 labelSize.height = [labelFont ascender] - [labelFont descender];
1254 if (labelSize.width > MAX_DRAG_LABEL_WIDTH){
1255 labelSize.width = MAX_DRAG_LABEL_WIDTH;
1256 clipLabelString = YES;
1259 NSSize imageSize, urlStringSize;
1260 imageSize.width = labelSize.width + DRAG_LABEL_BORDER_X * 2.0f;
1261 imageSize.height = labelSize.height + DRAG_LABEL_BORDER_Y * 2.0f;
1262 if (drawURLString) {
1263 urlStringSize.width = [urlString _web_widthWithFont: urlFont];
1264 urlStringSize.height = [urlFont ascender] - [urlFont descender];
1265 imageSize.height += urlStringSize.height;
1266 if (urlStringSize.width > MAX_DRAG_LABEL_WIDTH) {
1267 imageSize.width = MAX(MAX_DRAG_LABEL_WIDTH + DRAG_LABEL_BORDER_X * 2.0f, MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP);
1268 clipURLString = YES;
1270 imageSize.width = MAX(labelSize.width + DRAG_LABEL_BORDER_X * 2.0f, urlStringSize.width + DRAG_LABEL_BORDER_X * 2.0f);
1273 NSImage *dragImage = [[[NSImage alloc] initWithSize: imageSize] autorelease];
1274 [dragImage lockFocus];
1276 [[NSColor colorWithCalibratedRed: 0.7f green: 0.7f blue: 0.7f alpha: 0.8f] set];
1278 // Drag a rectangle with rounded corners/
1279 NSBezierPath *path = [NSBezierPath bezierPath];
1280 [path appendBezierPathWithOvalInRect: NSMakeRect(0.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)];
1281 [path appendBezierPathWithOvalInRect: NSMakeRect(0, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)];
1282 [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)];
1283 [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)];
1285 [path appendBezierPathWithRect: NSMakeRect(DRAG_LABEL_RADIUS, 0.0f, imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height)];
1286 [path appendBezierPathWithRect: NSMakeRect(0.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 10.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)];
1287 [path appendBezierPathWithRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS - 20.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 20.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)];
1290 NSColor *topColor = [NSColor colorWithCalibratedWhite:0.0f alpha:0.75f];
1291 NSColor *bottomColor = [NSColor colorWithCalibratedWhite:1.0f alpha:0.5f];
1292 if (drawURLString) {
1294 urlString = [WebStringTruncator centerTruncateString: urlString toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:urlFont];
1296 [urlString _web_drawDoubledAtPoint:NSMakePoint(DRAG_LABEL_BORDER_X, DRAG_LABEL_BORDER_Y - [urlFont descender])
1297 withTopColor:topColor bottomColor:bottomColor font:urlFont];
1300 if (clipLabelString)
1301 label = [WebStringTruncator rightTruncateString: label toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:labelFont];
1302 [label _web_drawDoubledAtPoint:NSMakePoint (DRAG_LABEL_BORDER_X, imageSize.height - DRAG_LABEL_BORDER_Y_OFFSET - [labelFont pointSize])
1303 withTopColor:topColor bottomColor:bottomColor font:labelFont];
1305 [dragImage unlockFocus];
1310 - (BOOL)_startDraggingImage:(NSImage *)wcDragImage at:(NSPoint)wcDragLoc operation:(NSDragOperation)op event:(NSEvent *)mouseDraggedEvent sourceIsDHTML:(BOOL)srcIsDHTML DHTMLWroteData:(BOOL)dhtmlWroteData
1312 WebHTMLView *topHTMLView = [self _topHTMLView];
1313 if (self != topHTMLView) {
1314 [topHTMLView _setMouseDownEvent:_private->mouseDownEvent];
1315 BOOL result = [topHTMLView _startDraggingImage:wcDragImage at:wcDragLoc operation:op event:mouseDraggedEvent sourceIsDHTML:srcIsDHTML DHTMLWroteData:dhtmlWroteData];
1316 [topHTMLView _setMouseDownEvent:nil];
1320 NSPoint mouseDownPoint = [self convertPoint:[_private->mouseDownEvent locationInWindow] fromView:nil];
1321 NSDictionary *element = [self elementAtPoint:mouseDownPoint allowShadowContent:YES];
1323 NSURL *linkURL = [element objectForKey:WebElementLinkURLKey];
1324 NSURL *imageURL = [element objectForKey:WebElementImageURLKey];
1325 BOOL isSelected = [[element objectForKey:WebElementIsSelectedKey] boolValue];
1327 [_private->draggingImageURL release];
1328 _private->draggingImageURL = nil;
1330 NSPoint mouseDraggedPoint = [self convertPoint:[mouseDraggedEvent locationInWindow] fromView:nil];
1331 _private->webCoreDragOp = op; // will be DragNone if WebCore doesn't care
1332 NSImage *dragImage = nil;
1333 NSPoint dragLoc = { 0, 0 }; // quiet gcc 4.0 warning
1335 // We allow WebCore to override the drag image, even if its a link, image or text we're dragging.
1336 // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp.
1337 // We could verify that ActionDHTML is allowed, although WebCore does claim to respect the action.
1339 dragImage = wcDragImage;
1340 // wcDragLoc is the cursor position relative to the lower-left corner of the image.
1341 // We add in the Y dimension because we are a flipped view, so adding moves the image down.
1344 dragLoc = NSMakePoint(mouseDraggedPoint.x - wcDragLoc.x, mouseDraggedPoint.y + wcDragLoc.y);
1346 dragLoc = NSMakePoint(mouseDownPoint.x - wcDragLoc.x, mouseDownPoint.y + wcDragLoc.y);
1347 _private->dragOffset = wcDragLoc;
1350 WebView *webView = [self _webView];
1351 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
1352 BOOL startedDrag = YES; // optimism - we almost always manage to start the drag
1354 // note per kwebster, the offset arg below is always ignored in positioning the image
1355 DOMNode *node = [element objectForKey:WebElementDOMNodeKey];
1356 WebHTMLView *innerHTMLView = (WebHTMLView *)[[[[node ownerDocument] webFrame] frameView] documentView];
1357 ASSERT([innerHTMLView isKindOfClass:[WebHTMLView class]]);
1359 && [node isKindOfClass:[DOMElement class]]
1360 && [(DOMElement *)node image]
1361 && (_private->dragSourceActionMask & WebDragSourceActionImage)) {
1363 if (!dhtmlWroteData) {
1364 // Select the image when it is dragged. This allows the image to be moved via MoveSelectionCommandImpl and this matches NSTextView's behavior.
1365 ASSERT(node != nil);
1366 [webView setSelectedDOMRange:[[node ownerDocument] _createRangeWithNode:node] affinity:NSSelectionAffinityDownstream];
1367 _private->draggingImageURL = [imageURL retain];
1369 WebArchive *archive;
1371 // If the image element comes from an ImageDocument, we don't want to
1372 // create a web archive from the image element.
1373 if ([[self _bridge] canSaveAsWebArchive])
1374 archive = [node webArchive];
1376 archive = [WebArchiver archiveMainResourceForFrame:[self _frame]];
1378 source = [pasteboard _web_declareAndWriteDragImageForElement:(DOMElement *)node
1379 URL:linkURL ? linkURL : imageURL
1380 title:[element objectForKey:WebElementImageAltStringKey]
1384 [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionImage fromPoint:mouseDownPoint withPasteboard:pasteboard];
1385 if (dragImage == nil)
1386 [self _web_DragImageForElement:(DOMElement *)node
1387 rect:[self convertRect:[[element objectForKey:WebElementImageRectKey] rectValue] fromView:innerHTMLView]
1388 event:_private->mouseDownEvent
1389 pasteboard:pasteboard
1391 offset:&_private->dragOffset];
1393 [self dragImage:dragImage
1396 event:_private->mouseDownEvent
1397 pasteboard:pasteboard
1400 } else if (linkURL && (_private->dragSourceActionMask & WebDragSourceActionLink)) {
1401 if (!dhtmlWroteData) {
1402 NSArray *types = [NSPasteboard _web_writableTypesForURL];
1403 [pasteboard declareTypes:types owner:self];
1404 [pasteboard _web_writeURL:linkURL andTitle:[element objectForKey:WebElementLinkLabelKey] types:types];
1406 [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionLink fromPoint:mouseDownPoint withPasteboard:pasteboard];
1407 if (dragImage == nil) {
1408 dragImage = [self _dragImageForLinkElement:element];
1409 NSSize offset = NSMakeSize([dragImage size].width / 2, -DRAG_LABEL_BORDER_Y);
1410 dragLoc = NSMakePoint(mouseDraggedPoint.x - offset.width, mouseDraggedPoint.y - offset.height);
1411 _private->dragOffset.x = offset.width;
1412 _private->dragOffset.y = -offset.height; // inverted because we are flipped
1414 // HACK: We should pass the mouseDown event instead of the mouseDragged! This hack gets rid of
1415 // a flash of the image at the mouseDown location when the drag starts.
1416 [self dragImage:dragImage
1419 event:mouseDraggedEvent
1420 pasteboard:pasteboard
1423 } else if (isSelected && (_private->dragSourceActionMask & WebDragSourceActionSelection)) {
1424 if (!dhtmlWroteData)
1425 [innerHTMLView _writeSelectionToPasteboard:pasteboard];
1426 [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionSelection fromPoint:mouseDownPoint withPasteboard:pasteboard];
1427 if (dragImage == nil) {
1428 dragImage = [innerHTMLView _selectionDraggingImage];
1429 NSRect draggingRect = [self convertRect:[innerHTMLView _selectionDraggingRect] fromView:innerHTMLView];
1430 dragLoc = NSMakePoint(NSMinX(draggingRect), NSMaxY(draggingRect));
1431 _private->dragOffset.x = mouseDownPoint.x - dragLoc.x;
1432 _private->dragOffset.y = dragLoc.y - mouseDownPoint.y; // inverted because we are flipped
1434 [self dragImage:dragImage
1437 event:_private->mouseDownEvent
1438 pasteboard:pasteboard
1441 } else if (srcIsDHTML) {
1442 ASSERT(_private->dragSourceActionMask & WebDragSourceActionDHTML);
1443 [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionDHTML fromPoint:mouseDownPoint withPasteboard:pasteboard];
1444 if (dragImage == nil) {
1445 // WebCore should have given us an image, but we'll make one up
1446 // FIXME: Oops! I removed this image from WebKit. Is this a dead code path?
1447 NSString *imagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_image" ofType:@"tiff"];
1448 dragImage = [[[NSImage alloc] initWithContentsOfFile:imagePath] autorelease];
1449 NSSize imageSize = [dragImage size];
1450 dragLoc = NSMakePoint(mouseDownPoint.x - imageSize.width / 2, mouseDownPoint.y + imageSize.height / 2);
1451 _private->dragOffset.x = imageSize.width / 2;
1452 _private->dragOffset.y = imageSize.height / 2;
1454 [self dragImage:dragImage
1457 event:_private->mouseDownEvent
1458 pasteboard:pasteboard
1462 // Only way I know if to get here is if the original element clicked on in the mousedown is no longer
1463 // under the mousedown point, so linkURL, imageURL and isSelected are all false/nil.
1469 - (void)_handleAutoscrollForMouseDragged:(NSEvent *)event
1471 [self autoscroll:event];
1472 [self _startAutoscrollTimer:event];
1475 - (BOOL)_mayStartDragAtEventLocation:(NSPoint)location
1477 WebHTMLView *topHTMLView = [self _topHTMLView];
1478 if (self != topHTMLView)
1479 return [topHTMLView _mayStartDragAtEventLocation:location];
1481 NSPoint mouseDownPoint = [self convertPoint:location fromView:nil];
1482 NSDictionary *mouseDownElement = [self elementAtPoint:mouseDownPoint allowShadowContent:YES];
1484 ASSERT([self _webView]);
1486 if ([mouseDownElement objectForKey:WebElementImageKey]
1487 && [mouseDownElement objectForKey:WebElementImageURLKey]
1488 && [[[self _webView] preferences] loadsImagesAutomatically]
1489 && (_private->dragSourceActionMask & WebDragSourceActionImage))
1492 if ([mouseDownElement objectForKey:WebElementLinkURLKey]
1493 && (_private->dragSourceActionMask & WebDragSourceActionLink))
1496 if ([[mouseDownElement objectForKey:WebElementIsSelectedKey] boolValue]
1497 && (_private->dragSourceActionMask & WebDragSourceActionSelection))
1503 - (WebPluginController *)_pluginController
1505 return _private->pluginController;
1508 - (void)_web_setPrintingModeRecursive
1510 [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];
1511 [super _web_setPrintingModeRecursive];
1514 - (void)_web_clearPrintingModeRecursive
1516 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];
1517 [super _web_clearPrintingModeRecursive];
1520 - (void)_layoutIfNeeded
1522 ASSERT(!_private->subviewsSetAside);
1524 if ([[self _bridge] needsLayout])
1525 _private->needsLayout = YES;
1526 if (_private->needsToApplyStyles || _private->needsLayout)
1530 - (void)_web_layoutIfNeededRecursive
1532 [self _layoutIfNeeded];
1533 [super _web_layoutIfNeededRecursive];
1536 - (void)_web_layoutIfNeededRecursive:(NSRect)displayRect testDirtyRect:(bool)testDirtyRect
1538 ASSERT(!_private->subviewsSetAside);
1540 displayRect = NSIntersectionRect(displayRect, [self bounds]);
1541 if (testDirtyRect) {
1542 NSRect dirtyRect = [self _dirtyRect];
1543 displayRect = NSIntersectionRect(displayRect, dirtyRect);
1545 if (!NSIsEmptyRect(displayRect))
1546 [self _layoutIfNeeded];
1548 [super _web_layoutIfNeededRecursive:displayRect testDirtyRect:NO];
1551 - (void)_startAutoscrollTimer: (NSEvent *)triggerEvent
1553 if (_private->autoscrollTimer == nil) {
1554 _private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL
1555 target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES] retain];
1556 _private->autoscrollTriggerEvent = [triggerEvent retain];
1560 // FIXME: _selectionRect is deprecated in favor of selectionRect, which is in protocol WebDocumentSelection.
1561 // We can't remove this yet because it's still in use by Mail.
1562 - (NSRect)_selectionRect
1564 return [self selectionRect];
1567 - (void)_stopAutoscrollTimer
1569 NSTimer *timer = _private->autoscrollTimer;
1570 _private->autoscrollTimer = nil;
1571 [_private->autoscrollTriggerEvent release];
1572 _private->autoscrollTriggerEvent = nil;
1579 // Guarantee that the autoscroll timer is invalidated, even if we don't receive
1580 // a mouse up event.
1581 BOOL isStillDown = CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft);
1583 [self _stopAutoscrollTimer];
1587 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
1588 location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
1589 modifierFlags:[[NSApp currentEvent] modifierFlags]
1590 timestamp:[NSDate timeIntervalSinceReferenceDate]
1591 windowNumber:[[self window] windowNumber]
1592 context:[[NSApp currentEvent] context]
1593 eventNumber:0 clickCount:0 pressure:0];
1594 [self mouseDragged:fakeEvent];
1599 // Copying can be done regardless of whether you can edit.
1600 return [self _hasSelection] && [[self _bridge] mayCopy];
1605 return [self _canCopy] && [self _isEditable];
1610 return [self _hasSelection] && [self _isEditable];
1615 return [self _hasSelectionOrInsertionPoint] && [self _isEditable];
1620 return [self _hasSelectionOrInsertionPoint] && [self _isEditable];
1623 - (BOOL)_canEditRichly
1625 return [self _canEdit] && [[self _bridge] isSelectionRichlyEditable];
1628 - (BOOL)_canAlterCurrentSelection
1630 return [self _hasSelectionOrInsertionPoint] && [self _isEditable];
1633 - (BOOL)_hasSelection
1635 return [[self _bridge] selectionState] == WebSelectionStateRange;
1638 - (BOOL)_hasSelectionOrInsertionPoint
1640 return [[self _bridge] selectionState] != WebSelectionStateNone;
1643 - (BOOL)_hasInsertionPoint
1645 return [[self _bridge] selectionState] == WebSelectionStateCaret;
1650 return [[self _webView] isEditable] || [[self _bridge] isSelectionEditable];
1653 - (BOOL)_isSelectionInPasswordField
1655 return [[self _bridge] isSelectionInPasswordField];
1658 - (BOOL)_isSelectionMisspelled
1660 NSString *selectedString = [self selectedString];
1661 unsigned length = [selectedString length];
1665 NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:selectedString
1669 inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]
1671 return range.length == length;
1674 - (NSArray *)_guessesForMisspelledSelection
1676 ASSERT([[self selectedString] length] != 0);
1677 return [[NSSpellChecker sharedSpellChecker] guessesForWord:[self selectedString]];
1680 - (void)_changeSpellingFromMenu:(id)sender
1682 ASSERT([[self selectedString] length] != 0);
1683 if ([self _shouldReplaceSelectionWithText:[sender title] givenAction:WebViewInsertActionPasted]) {
1684 [[self _bridge] replaceSelectionWithText:[sender title] selectReplacement:YES smartReplace:NO];
1688 - (void)_ignoreSpellingFromMenu:(id)sender
1690 ASSERT([[self selectedString] length] != 0);
1691 [[NSSpellChecker sharedSpellChecker] ignoreWord:[self selectedString] inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]];
1694 - (void)_learnSpellingFromMenu:(id)sender
1696 ASSERT([[self selectedString] length] != 0);
1697 [[NSSpellChecker sharedSpellChecker] learnWord:[self selectedString]];
1700 - (void)_lookUpInDictionaryFromMenu:(id)sender
1702 // This should only be called when there's a selection, but play it safe.
1703 if (![self _hasSelection]) {
1707 // Soft link to dictionary-display function to avoid linking another framework (ApplicationServices/LangAnalysis)
1708 static OSStatus (*__dictionaryServiceWindowShow)(id inWordString, NSRect inWordBoundary, UInt16 inLineDirection) = NULL;
1709 static const struct mach_header *frameworkImageHeader = NULL;
1710 static BOOL lookedForFunction = NO;
1711 if (!lookedForFunction) {
1712 __dictionaryServiceWindowShow = _NSSoftLinkingGetFrameworkFuncPtr(@"ApplicationServices", @"LangAnalysis", "_DCMDictionaryServiceWindowShow", &frameworkImageHeader);
1713 lookedForFunction = YES;
1715 if (!__dictionaryServiceWindowShow) {
1716 LOG_ERROR("Couldn't find _DCMDictionaryServiceWindowShow");
1720 // FIXME: must check for right-to-left here
1721 NSWritingDirection writingDirection = NSWritingDirectionLeftToRight;
1723 NSAttributedString *attrString = [self selectedAttributedString];
1724 // FIXME: the dictionary API expects the rect for the first line of selection. Passing
1725 // the rect for the entire selection, as we do here, positions the pop-up window near
1726 // the bottom of the selection rather than at the selected word.
1727 NSRect rect = [self convertRect:[[self _bridge] visibleSelectionRect] toView:nil];
1728 rect.origin = [[self window] convertBaseToScreen:rect.origin];
1729 NSData *data = [attrString RTFFromRange:NSMakeRange(0, [attrString length]) documentAttributes:nil];
1730 (void)__dictionaryServiceWindowShow(data, rect, (writingDirection == NSWritingDirectionRightToLeft) ? 1 : 0);
1733 - (BOOL)_transparentBackground
1735 return _private->transparentBackground;
1738 - (void)_setTransparentBackground:(BOOL)f
1740 _private->transparentBackground = f;
1743 - (NSImage *)_selectionDraggingImage
1745 if ([self _hasSelection]) {
1746 NSImage *dragImage = [[self _bridge] selectionImageForcingWhiteText:NO];
1747 [dragImage _web_dissolveToFraction:WebDragImageAlpha];
1753 - (NSRect)_selectionDraggingRect
1755 // Mail currently calls this method. We can eliminate it when Mail no longer calls it.
1756 return [self selectionImageRect];
1759 - (BOOL)_canIncreaseSelectionListLevel
1761 return ([self _canEditRichly] && [[self _bridge] canIncreaseSelectionListLevel]);
1764 - (BOOL)_canDecreaseSelectionListLevel
1766 return ([self _canEditRichly] && [[self _bridge] canDecreaseSelectionListLevel]);
1769 - (DOMNode *)_increaseSelectionListLevel
1771 if (![self _canEditRichly])
1774 WebFrameBridge *bridge = [self _bridge];
1775 return [bridge increaseSelectionListLevel];
1778 - (DOMNode *)_increaseSelectionListLevelOrdered
1780 if (![self _canEditRichly])
1783 WebFrameBridge *bridge = [self _bridge];
1784 return [bridge increaseSelectionListLevelOrdered];
1787 - (DOMNode *)_increaseSelectionListLevelUnordered
1789 if (![self _canEditRichly])
1792 WebFrameBridge *bridge = [self _bridge];
1793 return [bridge increaseSelectionListLevelUnordered];
1796 - (void)_decreaseSelectionListLevel
1798 if (![self _canEditRichly])
1801 WebFrameBridge *bridge = [self _bridge];
1802 [bridge decreaseSelectionListLevel];
1805 - (void)_setHighlighter:(id<WebHTMLHighlighter>)highlighter ofType:(NSString*)type
1807 if (!_private->highlighters)
1808 _private->highlighters = [[NSMutableDictionary alloc] init];
1809 [_private->highlighters setObject:highlighter forKey:type];
1812 - (void)_removeHighlighterOfType:(NSString*)type
1814 [_private->highlighters removeObjectForKey:type];
1817 - (BOOL)_web_firstResponderCausesFocusDisplay
1819 return [self _web_firstResponderIsSelfOrDescendantView] || [[self window] firstResponder] == [self _frameView];
1822 - (void)_updateActiveState
1824 // This method does the job of updating the view based on the view's firstResponder-ness and
1825 // the window key-ness of the window containing this view. This involves four kinds of
1826 // drawing updates right now, all handled in WebCore in response to the call over the bridge.
1828 // The four display attributes are as follows:
1830 // 1. The background color used to draw behind selected content (active | inactive color)
1831 // 2. Caret blinking (blinks | does not blink)
1832 // 3. The drawing of a focus ring around links in web pages.
1833 // 4. Changing the tint of controls from clear to aqua/graphite and vice versa
1835 // Also, this is responsible for letting the bridge know if the window has gained or lost focus
1836 // so we can send focus and blur events.
1838 NSWindow *window = [self window];
1839 BOOL windowIsKey = [window isKeyWindow];
1840 BOOL windowOrSheetIsKey = windowIsKey || [[window attachedSheet] isKeyWindow];
1842 BOOL isActive = !_private->resigningFirstResponder && windowIsKey && (_private->descendantBecomingFirstResponder || [self _web_firstResponderCausesFocusDisplay]);
1844 [[self _bridge] setWindowHasFocus:windowOrSheetIsKey];
1845 [[self _bridge] setIsActive:isActive];
1848 - (unsigned)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag
1850 return [[self _bridge] markAllMatchesForText:string caseSensitive:caseFlag];
1853 - (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue
1855 [[self _bridge] setMarkedTextMatchesAreHighlighted:newValue];
1858 - (BOOL)markedTextMatchesAreHighlighted
1860 return [[self _bridge] markedTextMatchesAreHighlighted];
1863 - (void)unmarkAllTextMatches
1865 return [[self _bridge] unmarkAllTextMatches];
1868 - (NSArray *)rectsForTextMatches
1870 return [[self _bridge] rectsForTextMatches];
1873 - (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard
1875 ASSERT([self _hasSelection]);
1876 NSArray *types = [self pasteboardTypesForSelection];
1878 // Don't write RTFD to the pasteboard when the copied attributed string has no attachments.
1879 NSAttributedString *attributedString = [self selectedAttributedString];
1880 NSMutableArray *mutableTypes = nil;
1881 if (![attributedString containsAttachments]) {
1882 mutableTypes = [types mutableCopy];
1883 [mutableTypes removeObject:NSRTFDPboardType];
1884 types = mutableTypes;
1887 [pasteboard declareTypes:types owner:nil];
1888 [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:attributedString];
1889 [mutableTypes release];
1894 if (_private->closed)
1896 [self _clearLastHitViewIfSelf];
1897 // FIXME: This is slow; should remove individual observers instead.
1898 [[NSNotificationCenter defaultCenter] removeObserver:self];
1899 [_private->pluginController destroyAllPlugins];
1900 // remove tooltips before clearing _private so removeTrackingRect: will work correctly
1901 [self removeAllToolTips];
1902 _private->closed = YES;
1907 @implementation NSView (WebHTMLViewFileInternal)
1909 - (void)_web_setPrintingModeRecursive
1911 [_subviews makeObjectsPerformSelector:@selector(_web_setPrintingModeRecursive)];
1914 - (void)_web_clearPrintingModeRecursive
1916 [_subviews makeObjectsPerformSelector:@selector(_web_clearPrintingModeRecursive)];
1919 - (void)_web_layoutIfNeededRecursive
1921 [_subviews makeObjectsPerformSelector:@selector(_web_layoutIfNeededRecursive)];
1924 - (void)_web_layoutIfNeededRecursive: (NSRect)rect testDirtyRect:(bool)testDirtyRect
1926 unsigned index, count;
1927 for (index = 0, count = [(NSArray *)_subviews count]; index < count; index++) {
1928 NSView *subview = [_subviews objectAtIndex:index];
1929 NSRect dirtiedSubviewRect = [subview convertRect: rect fromView: self];
1930 [subview _web_layoutIfNeededRecursive: dirtiedSubviewRect testDirtyRect:testDirtyRect];
1936 @implementation NSMutableDictionary (WebHTMLViewFileInternal)
1938 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key
1940 if (object == nil) {
1941 [self removeObjectForKey:key];
1943 [self setObject:object forKey:key];
1949 // The following is a workaround for
1950 // <rdar://problem/3429631> window stops getting mouse moved events after first tooltip appears
1951 // The trick is to define a category on NSToolTipPanel that implements setAcceptsMouseMovedEvents:.
1952 // Since the category will be searched before the real class, we'll prevent the flag from being
1953 // set on the tool tip panel.
1955 @interface NSToolTipPanel : NSPanel
1958 @interface NSToolTipPanel (WebHTMLViewFileInternal)
1961 @implementation NSToolTipPanel (WebHTMLViewFileInternal)
1963 - (void)setAcceptsMouseMovedEvents:(BOOL)flag
1965 // Do nothing, preventing the tool tip panel from trying to accept mouse-moved events.
1970 @interface NSArray (WebHTMLView)
1971 - (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object;
1974 @implementation WebHTMLView
1978 [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes]
1979 returnTypes:[[self class] _insertablePasteboardTypes]];
1980 _NSInitializeKillRing();
1983 - (void)_resetCachedWebPreferences:(NSNotification *)ignored
1985 WebPreferences *preferences = [[self _webView] preferences];
1986 // Check for nil because we might not yet have an associated webView when this is called
1987 if (preferences == nil) {
1988 preferences = [WebPreferences standardPreferences];
1990 _private->showsURLsInToolTips = [preferences showsURLsInToolTips];
1993 - (id)initWithFrame:(NSRect)frame
1995 self = [super initWithFrame:frame];
1999 // Make all drawing go through us instead of subviews.
2000 if (NSAppKitVersionNumber >= 711) {
2001 [self _setDrawsOwnDescendants:YES];
2004 _private = [[WebHTMLViewPrivate alloc] init];
2006 _private->pluginController = [[WebPluginController alloc] initWithDocumentView:self];
2007 _private->needsLayout = YES;
2008 [self _resetCachedWebPreferences:nil];
2009 [[NSNotificationCenter defaultCenter]
2010 addObserver:self selector:@selector(_resetCachedWebPreferences:)
2011 name:WebPreferencesChangedNotification object:nil];
2018 // We can't assert that close has already been called because
2019 // this view can be removed from it's superview, even though
2020 // it could be needed later, so close if needed.
2029 // We can't assert that close has already been called because
2030 // this view can be removed from it's superview, even though
2031 // it could be needed later, so close if needed.
2036 - (IBAction)takeFindStringFromSelection:(id)sender
2038 if (![self _hasSelection]) {
2043 [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self];
2046 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types
2048 [pasteboard declareTypes:types owner:nil];
2049 [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard];
2053 - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard
2055 if ([[self _bridge] isSelectionRichlyEditable])
2056 [self _pasteWithPasteboard:pasteboard allowPlainText:YES];
2058 [self _pasteAsPlainTextWithPasteboard:pasteboard];
2062 - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
2064 if (sendType != nil && [[self pasteboardTypesForSelection] containsObject:sendType] && [self _hasSelection]) {
2066 } else if (returnType != nil && [[[self class] _insertablePasteboardTypes] containsObject:returnType] && [self _isEditable]) {
2069 return [[self nextResponder] validRequestorForSendType:sendType returnType:returnType];
2072 - (void)selectAll:(id)sender
2077 // jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari
2078 // was using the old jumpToSelection selector in its menu. Newer versions of Safari will us the
2079 // selector centerSelectionInVisibleArea. We'll leave this old selector in place for two reasons:
2080 // (1) compatibility between older Safari and newer WebKit; (2) other WebKit-based applications
2081 // might be using the jumpToSelection: selector, and we don't want to break them.
2082 - (void)jumpToSelection:(id)sender
2084 [self centerSelectionInVisibleArea:sender];
2087 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
2089 SEL action = [item action];
2090 WebFrameBridge *bridge = [self _bridge];
2092 if (action == @selector(changeBaseWritingDirection:) // FIXME: check menu item based on writing direction
2093 || action == @selector(changeSpelling:)
2094 || action == @selector(_changeSpellingFromMenu:)
2095 || action == @selector(checkSpelling:)
2096 || action == @selector(complete:)
2097 || action == @selector(deleteBackward:)
2098 || action == @selector(deleteBackwardByDecomposingPreviousCharacter:)
2099 || action == @selector(deleteForward:)
2100 || action == @selector(deleteToBeginningOfLine:)
2101 || action == @selector(deleteToBeginningOfParagraph:)
2102 || action == @selector(deleteToEndOfLine:)
2103 || action == @selector(deleteToEndOfParagraph:)
2104 || action == @selector(deleteToMark:)
2105 || action == @selector(deleteWordBackward:)
2106 || action == @selector(deleteWordForward:)
2107 || action == @selector(insertBacktab:)
2108 || action == @selector(insertLineBreak:)
2109 || action == @selector(insertNewline:)
2110 || action == @selector(insertNewlineIgnoringFieldEditor:)
2111 || action == @selector(insertParagraphSeparator:)
2112 || action == @selector(insertTab:)
2113 || action == @selector(insertTabIgnoringFieldEditor:)
2114 || action == @selector(moveBackward:)
2115 || action == @selector(moveBackwardAndModifySelection:)
2116 || action == @selector(moveDown:)
2117 || action == @selector(moveDownAndModifySelection:)
2118 || action == @selector(moveForward:)
2119 || action == @selector(moveForwardAndModifySelection:)
2120 || action == @selector(moveLeft:)
2121 || action == @selector(moveLeftAndModifySelection:)
2122 || action == @selector(moveParagraphBackwardAndModifySelection:)
2123 || action == @selector(moveParagraphForwardAndModifySelection:)
2124 || action == @selector(moveRight:)
2125 || action == @selector(moveRightAndModifySelection:)
2126 || action == @selector(moveToBeginningOfDocument:)
2127 || action == @selector(moveToBeginningOfDocumentAndModifySelection:)
2128 || action == @selector(moveToBeginningOfSentence:)
2129 || action == @selector(moveToBeginningOfSentenceAndModifySelection:)
2130 || action == @selector(moveToBeginningOfLine:)
2131 || action == @selector(moveToBeginningOfLineAndModifySelection:)
2132 || action == @selector(moveToBeginningOfParagraph:)
2133 || action == @selector(moveToBeginningOfParagraphAndModifySelection:)
2134 || action == @selector(moveToEndOfDocument:)
2135 || action == @selector(moveToEndOfDocumentAndModifySelection:)
2136 || action == @selector(moveToEndOfSentence:)
2137 || action == @selector(moveToEndOfSentenceAndModifySelection:)
2138 || action == @selector(moveToEndOfLine:)
2139 || action == @selector(moveToEndOfLineAndModifySelection:)
2140 || action == @selector(moveToEndOfParagraph:)
2141 || action == @selector(moveToEndOfParagraphAndModifySelection:)
2142 || action == @selector(moveUp:)
2143 || action == @selector(moveUpAndModifySelection:)
2144 || action == @selector(moveWordBackward:)
2145 || action == @selector(moveWordBackwardAndModifySelection:)
2146 || action == @selector(moveWordForward:)
2147 || action == @selector(moveWordForwardAndModifySelection:)
2148 || action == @selector(moveWordLeft:)
2149 || action == @selector(moveWordLeftAndModifySelection:)
2150 || action == @selector(moveWordRight:)
2151 || action == @selector(moveWordRightAndModifySelection:)
2152 || action == @selector(pageDown:)
2153 || action == @selector(pageDownAndModifySelection:)
2154 || action == @selector(pageUp:)
2155 || action == @selector(pageUpAndModifySelection:)
2156 || action == @selector(pasteFont:)
2157 || action == @selector(showGuessPanel:)
2158 || action == @selector(toggleBaseWritingDirection:)
2159 || action == @selector(transpose:)
2160 || action == @selector(yank:)
2161 || action == @selector(yankAndSelect:)) {
2162 return [self _canEdit];
2163 } else if (action == @selector(alignCenter:)
2164 || action == @selector(alignLeft:)
2165 || action == @selector(alignJustified:)
2166 || action == @selector(alignRight:)
2167 || action == @selector(changeAttributes:)
2168 || action == @selector(changeColor:)
2169 || action == @selector(changeFont:)) {
2170 return [self _canEditRichly];
2171 } else if (action == @selector(capitalizeWord:)
2172 || action == @selector(lowercaseWord:)
2173 || action == @selector(uppercaseWord:)) {
2174 return [self _hasSelection] && [self _isEditable];
2175 } else if (action == @selector(centerSelectionInVisibleArea:)
2176 || action == @selector(jumpToSelection:)
2177 || action == @selector(copyFont:)
2178 || action == @selector(setMark:)) {
2179 return [self _hasSelection] || ([self _isEditable] && [self _hasInsertionPoint]);
2180 } else if (action == @selector(changeDocumentBackgroundColor:)) {
2181 return [[self _webView] isEditable] && [self _canEditRichly];
2182 } else if (action == @selector(copy:)) {
2183 return [bridge mayDHTMLCopy] || [self _canCopy];
2184 } else if (action == @selector(cut:)) {
2185 return [bridge mayDHTMLCut] || [self _canCut];
2186 } else if (action == @selector(delete:)) {
2187 return [self _canDelete];
2188 } else if (action == @selector(_ignoreSpellingFromMenu:)
2189 || action == @selector(_learnSpellingFromMenu:)
2190 || action == @selector(takeFindStringFromSelection:)) {
2191 return [self _hasSelection];
2192 } else if (action == @selector(paste:) || action == @selector(pasteAsPlainText:)) {
2193 return [bridge mayDHTMLPaste] || [self _canPaste];
2194 } else if (action == @selector(pasteAsRichText:)) {
2195 return [bridge mayDHTMLPaste] || ([self _canPaste] && [[self _bridge] isSelectionRichlyEditable]);
2196 } else if (action == @selector(performFindPanelAction:)) {
2197 // FIXME: Not yet implemented.
2199 } else if (action == @selector(selectToMark:)
2200 || action == @selector(swapWithMark:)) {
2201 return [self _hasSelectionOrInsertionPoint] && [[self _bridge] markDOMRange] != nil;
2202 } else if (action == @selector(subscript:)) {
2203 NSMenuItem *menuItem = (NSMenuItem *)item;
2204 if ([menuItem isKindOfClass:[NSMenuItem class]]) {
2205 DOMCSSStyleDeclaration *style = [self _emptyStyle];
2206 [style setVerticalAlign:@"sub"];
2207 [menuItem setState:[[self _bridge] selectionHasStyle:style]];
2209 return [self _canEditRichly];
2210 } else if (action == @selector(superscript:)) {
2211 NSMenuItem *menuItem = (NSMenuItem *)item;
2212 if ([menuItem isKindOfClass:[NSMenuItem class]]) {
2213 DOMCSSStyleDeclaration *style = [self _emptyStyle];
2214 [style setVerticalAlign:@"super"];
2215 [menuItem setState:[[self _bridge] selectionHasStyle:style]];
2217 return [self _canEditRichly];
2218 } else if (action == @selector(underline:)) {
2219 NSMenuItem *menuItem = (NSMenuItem *)item;
2220 if ([menuItem isKindOfClass:[NSMenuItem class]]) {
2221 DOMCSSStyleDeclaration *style = [self _emptyStyle];
2222 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""];
2223 [menuItem setState:[[self _bridge] selectionHasStyle:style]];
2225 return [self _canEditRichly];
2226 } else if (action == @selector(unscript:)) {
2227 NSMenuItem *menuItem = (NSMenuItem *)item;
2228 if ([menuItem isKindOfClass:[NSMenuItem class]]) {
2229 DOMCSSStyleDeclaration *style = [self _emptyStyle];
2230 [style setVerticalAlign:@"baseline"];
2231 [menuItem setState:[[self _bridge] selectionHasStyle:style]];
2233 return [self _canEditRichly];
2234 } else if (action == @selector(_lookUpInDictionaryFromMenu:)) {
2235 return [self _hasSelection];
2241 - (BOOL)acceptsFirstResponder
2243 // Don't accept first responder when we first click on this view.
2244 // We have to pass the event down through WebCore first to be sure we don't hit a subview.
2245 // Do accept first responder at any other time, for example from keyboard events,
2246 // or from calls back from WebCore once we begin mouse-down event handling.
2247 NSEvent *event = [NSApp currentEvent];
2248 if ([event type] == NSLeftMouseDown
2249 && !_private->handlingMouseDownEvent
2250 && NSPointInRect([event locationInWindow], [self convertRect:[self visibleRect] toView:nil])) {
2256 - (BOOL)maintainsInactiveSelection
2258 // This method helps to determine whether the WebHTMLView should maintain
2259 // an inactive selection when it's not first responder.
2260 // Traditionally, these views have not maintained such selections,
2261 // clearing them when the view was not first responder. However,
2262 // to fix bugs like this one:
2263 // <rdar://problem/3672088>: "Editable WebViews should maintain a selection even
2264 // when they're not firstResponder"
2265 // it was decided to add a switch to act more like an NSTextView.
2266 id nextResponder = [[self window] _newFirstResponderAfterResigning];
2268 // Predict the case where we are losing first responder status only to
2269 // gain it back again. Want to keep the selection in that case.
2270 if ([nextResponder isKindOfClass:[NSScrollView class]]) {
2271 id contentView = [nextResponder contentView];
2273 nextResponder = contentView;
2276 if ([nextResponder isKindOfClass:[NSClipView class]]) {
2277 id documentView = [nextResponder documentView];
2279 nextResponder = documentView;
2283 if (nextResponder == self)
2286 return [[self _webView] maintainsInactiveSelection] || [[self _bridge] isSelectionEditable];
2289 - (void)addMouseMovedObserver
2291 if (!_private->dataSource || ![self _isTopHTMLView])
2294 // Unless the Dashboard asks us to do this for all windows, keep an observer going only for the key window.
2295 if (!([[self window] isKeyWindow] || [[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows]))
2298 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mouseMovedNotification:)
2299 name:WKMouseMovedNotification() object:nil];
2300 [self _frameOrBoundsChanged];
2303 - (void)removeMouseMovedObserver
2305 // Don't remove the observer if we're running the Dashboard.
2306 // FIXME: Right for the windowDidResignKey: case, but wrong for the viewWillMoveToWindow: case.
2307 if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows])
2310 [[self _webView] _mouseDidMoveOverElement:nil modifierFlags:0];
2311 [[NSNotificationCenter defaultCenter] removeObserver:self
2312 name:WKMouseMovedNotification() object:nil];
2315 - (void)addSuperviewObservers
2317 // We watch the bounds of our superview, so that we can do a layout when the size
2318 // of the superview changes. This is different from other scrollable things that don't
2319 // need this kind of thing because their layout doesn't change.
2321 // We need to pay attention to both height and width because our "layout" has to change
2322 // to extend the background the full height of the space and because some elements have
2323 // sizes that are based on the total size of the view.
2325 NSView *superview = [self superview];
2326 if (superview && [self window]) {
2327 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged)
2328 name:NSViewFrameDidChangeNotification object:superview];
2329 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged)
2330 name:NSViewBoundsDidChangeNotification object:superview];
2332 // In addition to registering for frame/bounds change notifications, call -_frameOrBoundsChanged.
2333 // It will check the current size/scroll against the previous layout's size/scroll. We need to
2334 // do this here to catch the case where the WebView is laid out at one size, removed from its
2335 // window, resized, and inserted into another window. Our frame/bounds changed notifications
2336 // will not be sent in that situation, since we only watch for changes while in the view hierarchy.
2337 [self _frameOrBoundsChanged];
2341 - (void)removeSuperviewObservers
2343 NSView *superview = [self superview];
2344 if (superview && [self window]) {
2345 [[NSNotificationCenter defaultCenter] removeObserver:self
2346 name:NSViewFrameDidChangeNotification object:superview];
2347 [[NSNotificationCenter defaultCenter] removeObserver:self
2348 name:NSViewBoundsDidChangeNotification object:superview];
2352 - (void)addWindowObservers
2354 NSWindow *window = [self window];
2356 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeKey:)
2357 name:NSWindowDidBecomeKeyNotification object:nil];
2358 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResignKey:)
2359 name:NSWindowDidResignKeyNotification object:nil];
2360 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:)
2361 name:NSWindowWillCloseNotification object:window];
2365 - (void)removeWindowObservers
2367 NSWindow *window = [self window];
2369 [[NSNotificationCenter defaultCenter] removeObserver:self
2370 name:NSWindowDidBecomeKeyNotification object:nil];
2371 [[NSNotificationCenter defaultCenter] removeObserver:self
2372 name:NSWindowDidResignKeyNotification object:nil];
2373 [[NSNotificationCenter defaultCenter] removeObserver:self
2374 name:NSWindowWillCloseNotification object:window];
2378 - (void)viewWillMoveToSuperview:(NSView *)newSuperview
2380 [self removeSuperviewObservers];
2383 - (void)viewDidMoveToSuperview
2385 // Do this here in case the text size multiplier changed when a non-HTML
2386 // view was installed.
2387 if ([self superview] != nil) {
2388 [self _updateTextSizeMultiplier];
2389 [self addSuperviewObservers];
2393 - (void)viewWillMoveToWindow:(NSWindow *)window
2395 // Don't do anything if we aren't initialized. This happens
2396 // when decoding a WebView. When WebViews are decoded their subviews
2397 // are created by initWithCoder: and so won't be normally
2398 // initialized. The stub views are discarded by WebView.
2400 // FIXME: Some of these calls may not work because this view may be already removed from it's superview.
2401 [self removeMouseMovedObserver];
2402 [self removeWindowObservers];
2403 [self removeSuperviewObservers];
2404 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_updateMouseoverWithFakeEvent) object:nil];
2406 [[self _pluginController] stopAllPlugins];
2410 - (void)viewDidMoveToWindow
2412 // Don't do anything if we aren't initialized. This happens
2413 // when decoding a WebView. When WebViews are decoded their subviews
2414 // are created by initWithCoder: and so won't be normally
2415 // initialized. The stub views are discarded by WebView.
2417 [self _stopAutoscrollTimer];
2418 if ([self window]) {
2419 _private->lastScrollPosition = [[self superview] bounds].origin;
2420 [self addWindowObservers];
2421 [self addSuperviewObservers];
2422 [self addMouseMovedObserver];
2424 // Schedule this update, rather than making the call right now.
2425 // The reason is that placing the caret in the just-installed view requires
2426 // the HTML/XML document to be available on the WebCore side, but it is not
2427 // at the time this code is running. However, it will be there on the next
2428 // crank of the run loop. Doing this helps to make a blinking caret appear
2429 // in a new, empty window "automatic".
2430 [self performSelector:@selector(_updateActiveState) withObject:nil afterDelay:0];
2432 [[self _pluginController] startAllPlugins];
2434 _private->lastScrollPosition = NSZeroPoint;
2439 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
2441 [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewWillMoveToHostWindow:) withObject:hostWindow];
2444 - (void)viewDidMoveToHostWindow
2446 [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewDidMoveToHostWindow) withObject:nil];
2450 - (void)addSubview:(NSView *)view
2452 [super addSubview:view];
2454 if ([WebPluginController isPlugInView:view]) {
2455 [[self _pluginController] addPlugin:view];
2459 - (void)willRemoveSubview:(NSView *)subview
2461 if ([WebPluginController isPlugInView:subview])
2462 [[self _pluginController] destroyPlugin:subview];
2463 [super willRemoveSubview:subview];
2466 - (void)reapplyStyles
2468 if (!_private->needsToApplyStyles) {
2473 double start = CFAbsoluteTimeGetCurrent();
2476 [[self _bridge] reapplyStylesForDeviceType:
2477 _private->printing ? WebCoreDevicePrinter : WebCoreDeviceScreen];
2480 double thisTime = CFAbsoluteTimeGetCurrent() - start;
2481 LOG(Timing, "%s apply style seconds = %f", [self URL], thisTime);
2484 _private->needsToApplyStyles = NO;
2487 // Do a layout, but set up a new fixed width for the purposes of doing printing layout.
2488 // minPageWidth==0 implies a non-printing layout
2489 - (void)layoutToMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)adjustViewSize
2491 [self reapplyStyles];
2493 // Ensure that we will receive mouse move events. Is this the best place to put this?
2494 [[self window] setAcceptsMouseMovedEvents: YES];
2495 WKSetNSWindowShouldPostEventNotifications([self window], YES);
2497 if (!_private->needsLayout) {
2502 double start = CFAbsoluteTimeGetCurrent();
2505 LOG(View, "%@ doing layout", self);
2507 if (minPageWidth > 0.0) {
2508 [[self _bridge] forceLayoutWithMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize];
2510 [[self _bridge] forceLayoutAdjustingViewSize:adjustViewSize];
2512 _private->needsLayout = NO;
2514 if (!_private->printing) {
2515 // get size of the containing dynamic scrollview, so
2516 // appearance and disappearance of scrollbars will not show up
2518 NSSize newLayoutFrameSize = [[[self superview] superview] frame].size;
2519 if (_private->laidOutAtLeastOnce && !NSEqualSizes(_private->lastLayoutFrameSize, newLayoutFrameSize)) {
2520 [[self _bridge] sendResizeEvent];
2521 if ([[self _bridge] needsLayout])
2522 [[self _bridge] forceLayoutAdjustingViewSize:NO];
2524 _private->laidOutAtLeastOnce = YES;
2525 _private->lastLayoutSize = [(NSClipView *)[self superview] documentVisibleRect].size;
2526 _private->lastLayoutFrameSize = newLayoutFrameSize;
2530 double thisTime = CFAbsoluteTimeGetCurrent() - start;
2531 LOG(Timing, "%s layout seconds = %f", [self URL], thisTime);
2537 [self layoutToMinimumPageWidth:0.0f maximumPageWidth:0.0f adjustingViewSize:NO];
2540 - (NSMenu *)menuForEvent:(NSEvent *)event
2542 [_private->compController endRevertingChange:NO moveLeft:NO];
2544 _private->handlingMouseDownEvent = YES;
2545 BOOL handledEvent = [[self _bridge] sendContextMenuEvent:event];
2546 _private->handlingMouseDownEvent = NO;
2550 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
2551 NSDictionary *element = [self elementAtPoint:point];
2552 return [[self _webView] _menuForElement:element defaultItems:nil];
2555 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag
2557 if (![string length])
2560 return [[self _bridge] searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag];
2563 - (void)deselectText
2565 [[self _bridge] deselectText];
2570 return [[self _webView] drawsBackground];
2573 - (void)setNeedsDisplay:(BOOL)flag
2575 LOG(View, "%@ flag = %d", self, (int)flag);
2576 [super setNeedsDisplay: flag];
2579 - (void)setNeedsLayout: (BOOL)flag
2581 LOG(View, "%@ flag = %d", self, (int)flag);
2582 _private->needsLayout = flag;
2586 - (void)setNeedsToApplyStyles: (BOOL)flag
2588 LOG(View, "%@ flag = %d", self, (int)flag);
2589 _private->needsToApplyStyles = flag;
2592 - (void)drawSingleRect:(NSRect)rect
2594 [NSGraphicsContext saveGraphicsState];
2597 ASSERT([[self superview] isKindOfClass:[WebClipView class]]);
2599 [(WebClipView *)[self superview] setAdditionalClip:rect];
2602 if ([self _transparentBackground]) {
2603 [[NSColor clearColor] set];
2607 [[self _bridge] drawRect:rect];
2608 WebView *webView = [self _webView];
2609 [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView convertRect:rect fromView:self]];
2610 [(WebClipView *)[self superview] resetAdditionalClip];
2612 [NSGraphicsContext restoreGraphicsState];
2614 [(WebClipView *)[self superview] resetAdditionalClip];
2615 [NSGraphicsContext restoreGraphicsState];
2616 LOG_ERROR("Exception caught while drawing: %@", localException);
2617 [localException raise];
2621 - (void)drawRect:(NSRect)rect
2623 LOG(View, "%@ drawing", self);
2625 const NSRect *rects;
2627 [self getRectsBeingDrawn:&rects count:&count];
2629 BOOL subviewsWereSetAside = _private->subviewsSetAside;
2630 if (subviewsWereSetAside)
2631 [self _restoreSubviews];
2634 double start = CFAbsoluteTimeGetCurrent();
2637 // If count == 0 here, use the rect passed in for drawing. This is a workaround for:
2638 // <rdar://problem/3908282> REGRESSION (Mail): No drag image dragging selected text in Blot and Mail
2639 // The reason for the workaround is that this method is called explicitly from the code
2640 // to generate a drag image, and at that time, getRectsBeingDrawn:count: will return a zero count.
2641 const int cRectThreshold = 10;
2642 const float cWastedSpaceThreshold = 0.75f;
2643 BOOL useUnionedRect = (count <= 1) || (count > cRectThreshold);
2644 if (!useUnionedRect) {
2645 // Attempt to guess whether or not we should use the unioned rect or the individual rects.
2646 // We do this by computing the percentage of "wasted space" in the union. If that wasted space
2647 // is too large, then we will do individual rect painting instead.
2648 float unionPixels = (rect.size.width * rect.size.height);
2649 float singlePixels = 0;
2650 for (int i = 0; i < count; ++i)
2651 singlePixels += rects[i].size.width * rects[i].size.height;
2652 float wastedSpace = 1 - (singlePixels / unionPixels);
2653 if (wastedSpace <= cWastedSpaceThreshold)
2654 useUnionedRect = YES;
2658 [self drawSingleRect:rect];
2660 for (int i = 0; i < count; ++i)
2661 [self drawSingleRect:rects[i]];
2664 double thisTime = CFAbsoluteTimeGetCurrent() - start;
2665 LOG(Timing, "%s draw seconds = %f", widget->part()->baseURL().URL().latin1(), thisTime);
2668 if (subviewsWereSetAside)
2669 [self _setAsideSubviews];
2672 // Turn off the additional clip while computing our visibleRect.
2673 - (NSRect)visibleRect
2675 if (!([[self superview] isKindOfClass:[WebClipView class]]))
2676 return [super visibleRect];
2678 WebClipView *clipView = (WebClipView *)[self superview];
2680 BOOL hasAdditionalClip = [clipView hasAdditionalClip];
2681 if (!hasAdditionalClip) {
2682 return [super visibleRect];
2685 NSRect additionalClip = [clipView additionalClip];
2686 [clipView resetAdditionalClip];
2687 NSRect visibleRect = [super visibleRect];
2688 [clipView setAdditionalClip:additionalClip];
2697 - (void)windowDidBecomeKey:(NSNotification *)notification
2699 NSWindow *keyWindow = [notification object];
2701 if (keyWindow == [self window])
2702 [self addMouseMovedObserver];
2704 if (keyWindow == [self window] || keyWindow == [[self window] attachedSheet])
2705 [self _updateActiveState];
2708 - (void)windowDidResignKey:(NSNotification *)notification
2710 NSWindow *formerKeyWindow = [notification object];
2712 if (formerKeyWindow == [self window])
2713 [self removeMouseMovedObserver];
2715 if (formerKeyWindow == [self window] || formerKeyWindow == [[self window] attachedSheet]) {
2716 [self _updateActiveState];
2717 [_private->compController endRevertingChange:NO moveLeft:NO];
2721 - (void)windowWillClose:(NSNotification *)notification
2723 [_private->compController endRevertingChange:NO moveLeft:NO];
2724 [[self _pluginController] destroyAllPlugins];
2727 - (void)scrollWheel:(NSEvent *)event
2731 if (![[self _bridge] sendScrollWheelEvent:event]) {
2732 [[self nextResponder] scrollWheel:event];
2738 - (BOOL)_isSelectionEvent:(NSEvent *)event
2740 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
2741 return [[[self elementAtPoint:point allowShadowContent:YES] objectForKey:WebElementIsSelectedKey] boolValue];
2744 - (BOOL)acceptsFirstMouse:(NSEvent *)event
2746 NSView *hitView = [self _hitViewForEvent:event];
2747 WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil;
2749 if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysAcceptsFirstMouse])
2752 if (hitHTMLView != nil) {
2753 [hitHTMLView _setMouseDownEvent:event];
2754 [[hitHTMLView _bridge] setActivationEventNumber:[event eventNumber]];
2755 BOOL result = [hitHTMLView _isSelectionEvent:event] ? [[hitHTMLView _bridge] eventMayStartDrag:event] : NO;
2756 [hitHTMLView _setMouseDownEvent:nil];
2759 return [hitView acceptsFirstMouse:event];
2762 - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event
2764 NSView *hitView = [self _hitViewForEvent:event];
2765 WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil;
2766 if (hitHTMLView != nil) {
2767 [hitHTMLView _setMouseDownEvent:event];
2768 BOOL result = [hitHTMLView _isSelectionEvent:event] ? [[hitHTMLView _bridge] eventMayStartDrag:event] : NO;
2769 [hitHTMLView _setMouseDownEvent:nil];
2772 return [hitView shouldDelayWindowOrderingForEvent:event];
2775 - (void)mouseDown:(NSEvent *)event
2779 _private->handlingMouseDownEvent = YES;
2781 // Record the mouse down position so we can determine drag hysteresis.
2782 [self _setMouseDownEvent:event];
2784 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
2785 if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
2788 [_private->compController endRevertingChange:NO moveLeft:NO];
2790 // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here.
2791 // We don't want to pass them along to KHTML a second time.
2792 if (!([event modifierFlags] & NSControlKeyMask)) {
2793 _private->ignoringMouseDraggedEvents = NO;
2795 // Don't do any mouseover while the mouse is down.
2796 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_updateMouseoverWithFakeEvent) object:nil];
2798 // Let KHTML get a chance to deal with the event. This will call back to us
2799 // to start the autoscroll timer if appropriate.
2800 [[self _bridge] mouseDown:event];
2804 [_private->firstResponderTextViewAtMouseDownTime release];
2805 _private->firstResponderTextViewAtMouseDownTime = nil;
2807 _private->handlingMouseDownEvent = NO;
2812 - (void)dragImage:(NSImage *)dragImage
2814 offset:(NSSize)offset
2815 event:(NSEvent *)event
2816 pasteboard:(NSPasteboard *)pasteboard
2818 slideBack:(BOOL)slideBack
2820 [self _stopAutoscrollTimer];
2822 WebHTMLView *topHTMLView = [self _topHTMLView];
2823 if (self != topHTMLView) {
2824 [topHTMLView dragImage:dragImage at:[self convertPoint:at toView:topHTMLView]
2825 offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack];
2829 WebView *webView = [self _webView];
2831 [webView _setInitiatedDrag:YES];
2833 // Retain this view during the drag because it may be released before the drag ends.
2836 id UIDelegate = [webView UIDelegate];
2837 // If a delegate takes over the drag but never calls draggedImage: endedAt:, we'll leak the WebHTMLView.
2838 if ([UIDelegate respondsToSelector:@selector(webView:dragImage:at:offset:event:pasteboard:source:slideBack:forView:)])
2839 [UIDelegate webView:webView dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack forView:self];
2841 [super dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack];
2844 - (void)mouseDragged:(NSEvent *)event
2846 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
2847 if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
2852 if (!_private->ignoringMouseDraggedEvents)
2853 [[self _bridge] mouseDragged:event];
2858 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
2860 ASSERT([self _isTopHTMLView]);
2862 if (_private->webCoreDragOp == NSDragOperationNone)
2863 return NSDragOperationGeneric | NSDragOperationCopy;
2864 return _private->webCoreDragOp;
2867 - (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenLoc
2869 ASSERT([self _isTopHTMLView]);
2871 NSPoint windowImageLoc = [[self window] convertScreenToBase:screenLoc];
2872 NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + _private->dragOffset.x, windowImageLoc.y + _private->dragOffset.y);
2873 [[self _bridge] dragSourceMovedTo:windowMouseLoc];
2876 - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
2878 ASSERT(![self _webView] || [self _isTopHTMLView]);
2880 NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint];
2881 NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + _private->dragOffset.x, windowImageLoc.y + _private->dragOffset.y);
2882 [[self _bridge] dragSourceEndedAt:windowMouseLoc operation:operation];
2884 _private->initiatedDrag = NO;
2885 [[self _webView] _setInitiatedDrag:NO];
2887 // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event.
2888 _private->ignoringMouseDraggedEvents = YES;
2890 // Once the dragging machinery kicks in, we no longer get mouse drags or the up event.
2891 // WebCore expects to get balanced down/up's, so we must fake up a mouseup.
2892 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
2893 location:windowMouseLoc
2894 modifierFlags:[[NSApp currentEvent] modifierFlags]
2895 timestamp:[NSDate timeIntervalSinceReferenceDate]
2896 windowNumber:[[self window] windowNumber]
2897 context:[[NSApp currentEvent] context]
2898 eventNumber:0 clickCount:0 pressure:0];
2899 [self mouseUp:fakeEvent]; // This will also update the mouseover state.
2901 // Balance the previous retain from when the drag started.
2905 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
2907 ASSERT([self _isTopHTMLView]);
2908 ASSERT(_private->draggingImageURL);
2910 NSFileWrapper *wrapper = [[self _dataSource] _fileWrapperForURL:_private->draggingImageURL];
2911 if (wrapper == nil) {
2912 LOG_ERROR("Failed to create image file. Did the source image change while dragging? (<rdar://problem/4244861>)");
2916 // FIXME: Report an error if we fail to create a file.
2917 NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]];
2918 path = [[NSFileManager defaultManager] _webkit_pathWithUniqueFilenameForPath:path];
2919 if (![wrapper writeToFile:path atomically:NO updateFilenames:YES])
2920 LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToFile:atomically:updateFilenames:]");
2922 return [NSArray arrayWithObject:[path lastPathComponent]];
2925 - (void)mouseUp:(NSEvent *)event
2927 [self _setMouseDownEvent:nil];
2929 NSInputManager *currentInputManager = [NSInputManager currentInputManager];
2930 if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
2935 [self _stopAutoscrollTimer];
2936 [[self _bridge] mouseUp:event];
2937 [self _updateMouseoverWithFakeEvent];
2942 - (void)mouseMovedNotification:(NSNotification *)notification
2944 [self _updateMouseoverWithEvent:[[notification userInfo] objectForKey:@"NSEvent"]];
2947 // returning YES from this method is the way we tell AppKit that it is ok for this view
2948 // to be in the key loop even when "tab to all controls" is not on.
2949 - (BOOL)needsPanelToBecomeKey
2954 - (NSView *)nextValidKeyView
2957 BOOL lookInsideWebFrameViews = YES;
2958 if ([self isHiddenOrHasHiddenAncestor]) {
2959 lookInsideWebFrameViews = NO;
2960 } else if ([self _frame] == [[self _webView] mainFrame]) {
2961 // Check for case where first responder is last frame in a frameset, and we are
2962 // the top-level documentView.
2963 NSResponder *firstResponder = [[self window] firstResponder];
2964 if ((firstResponder != self) && [firstResponder isKindOfClass:[WebHTMLView class]] && ([(NSView *)firstResponder nextKeyView] == nil)) {
2965 lookInsideWebFrameViews = NO;
2969 if (lookInsideWebFrameViews) {
2970 view = [[self _bridge] nextKeyViewInsideWebFrameViews];
2974 view = [super nextValidKeyView];
2975 // If there's no next view wired up, we must be in the last subframe, or we are
2976 // being called at an unusual time when the views have not yet been wired together.
2977 // There's no direct link to the next valid key view; get it from the bridge.
2978 // Note that view == self here when nextKeyView returns nil, due to AppKit oddness.
2979 // We'll check for both nil and self in case the AppKit oddness goes away.
2980 // WebFrameView has this same kind of logic for the previousValidKeyView case.
2981 if (view == nil || view == self) {
2982 view = [[self _bridge] nextValidKeyViewOutsideWebFrameViews];
2989 - (NSView *)previousValidKeyView
2992 if (![self isHiddenOrHasHiddenAncestor])
2993 view = [[self _bridge] previousKeyViewInsideWebFrameViews];
2995 view = [super previousValidKeyView];
2999 - (BOOL)becomeFirstResponder
3002 if (![[self _webView] _isPerformingProgrammaticFocus] && !_private->willBecomeFirstResponderForNodeFocus) {
3003 switch ([[self window] keyViewSelectionDirection]) {
3004 case NSDirectSelection:
3006 case NSSelectingNext:
3007 view = [[self _bridge] nextKeyViewInsideWebFrameViews];
3009 case NSSelectingPrevious:
3010 view = [[self _bridge] previousKeyViewInsideWebFrameViews];
3014 _private->willBecomeFirstResponderForNodeFocus = NO;
3016 [[self window] makeFirstResponder:view];
3017 [self _updateActiveState];
3018 [self _updateFontPanel];
3019 _private->startNewKillRingSequence = YES;
3023 - (BOOL)resignFirstResponder
3025 BOOL resign = [super resignFirstResponder];
3027 [_private->compController endRevertingChange:NO moveLeft:NO];
3028 _private->resigningFirstResponder = YES;
3029 if (![self maintainsInactiveSelection]) {
3030 if ([[self _webView] _isPerformingProgrammaticFocus])
3031 [self deselectText];
3035 [self _updateActiveState];
3036 _private->resigningFirstResponder = NO;
3037 _private->willBecomeFirstResponderForNodeFocus = NO;
3042 - (void)setDataSource:(WebDataSource *)dataSource
3044 ASSERT(!_private->dataSource);
3045 _private->dataSource = [dataSource retain];
3046 [_private->pluginController setDataSource:dataSource];
3047 [self addMouseMovedObserver];
3050 - (void)dataSourceUpdated:(WebDataSource *)dataSource
3054 // This is an override of an NSControl method that wants to repaint the entire view when the window resigns/becomes
3055 // key. WebHTMLView is an NSControl only because it hosts NSCells that are painted by WebCore's Aqua theme
3056 // renderer (and those cells must be hosted by an enclosing NSControl in order to paint properly).
3057 - (void)updateCell:(NSCell*)cell
3061 // Does setNeedsDisplay:NO as a side effect when printing is ending.
3062 // pageWidth != 0 implies we will relayout to a new width
3063 - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize
3065 WebFrame *frame = [self _frame];
3066 NSArray *subframes = [frame childFrames];
3067 unsigned n = [subframes count];
3069 for (i = 0; i != n; ++i) {
3070 WebFrame *subframe = [subframes objectAtIndex:i];
3071 WebFrameView *frameView = [subframe frameView];
3072 if ([[subframe dataSource] _isDocumentHTML]) {
3073 [(WebHTMLView *)[frameView documentView] _setPrinting:printing minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:adjustViewSize];
3077 if (printing != _private->printing) {
3078 [_private->pageRects release];
3079 _private->pageRects = nil;
3080 _private->printing = printing;
3081 [self setNeedsToApplyStyles:YES];
3082 [self setNeedsLayout:YES];
3083 [self layoutToMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize];
3085 // Can't do this when starting printing or nested printing won't work, see 3491427.
3086 [self setNeedsDisplay:NO];
3091 - (BOOL)canPrintHeadersAndFooters
3096 // This is needed for the case where the webview is embedded in the view that's being printed.
3097 // It shouldn't be called when the webview is being printed directly.
3098 - (void)adjustPageHeightNew:(float *)newBottom top:(float)oldTop bottom:(float)oldBottom limit:(float)bottomLimit
3100 // This helps when we print as part of a larger print process.
3101 // If the WebHTMLView itself is what we're printing, then we will never have to do this.
3102 BOOL wasInPrintingMode = _private->printing;
3103 if (!wasInPrintingMode)
3104 [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];
3106 [[self _bridge] adjustPageHeightNew:newBottom top:oldTop bottom:oldBottom limit:bottomLimit];
3108 if (!wasInPrintingMode)
3109 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];
3112 - (float)_availablePaperWidthForPrintOperation:(NSPrintOperation *)printOperation
3114 NSPrintInfo *printInfo = [printOperation printInfo];
3115 return [printInfo paperSize].width - [printInfo leftMargin] - [printInfo rightMargin];
3118 - (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation
3120 float viewWidth = NSWidth([self bounds]);
3121 if (viewWidth < 1) {
3122 LOG_ERROR("%@ has no width when printing", self);
3126 float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
3127 float maxShrinkToFitScaleFactor = 1.0f / PrintingMaximumShrinkFactor;
3128 float shrinkToFitScaleFactor = [self _availablePaperWidthForPrintOperation:printOperation]/viewWidth;
3129 return userScaleFactor * MAX(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor);
3132 // FIXME 3491344: This is a secret AppKit-internal method that we need to override in order
3133 // to get our shrink-to-fit to work with a custom pagination scheme. We can do this better
3134 // if AppKit makes it SPI/API.
3135 - (float)_provideTotalScaleFactorForPrintOperation:(NSPrintOperation *)printOperation
3137 return [self _scaleFactorForPrintOperation:printOperation];
3140 // This is used for Carbon printing. At some point we might want to make this public API.
3141 - (void)setPageWidthForPrinting:(float)pageWidth
3143 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO];
3144 [self _setPrinting:YES minimumPageWidth:pageWidth maximumPageWidth:pageWidth adjustViewSize:YES];
3147 - (void)_endPrintMode
3149 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES];
3150 [[self window] setAutodisplay:YES];
3153 - (void)_delayedEndPrintMode:(NSPrintOperation *)initiatingOperation
3155 ASSERT_ARG(initiatingOperation, initiatingOperation != nil);
3156 NSPrintOperation *currentOperation = [NSPrintOperation currentOperation];
3157 if (initiatingOperation == currentOperation) {
3158 // The print operation is still underway. We don't expect this to ever happen, hence the assert, but we're
3159 // being extra paranoid here since the printing code is so fragile. Delay the cleanup
3161 ASSERT_NOT_REACHED();
3162 [self performSelector:@selector(_delayedEndPrintMode:) withObject:initiatingOperation afterDelay:0];
3163 } else if ([currentOperation view] == self) {
3164 // A new print job has started, but it is printing the same WebHTMLView again. We don't expect
3165 // this to ever happen, hence the assert, but we're being extra paranoid here since the printing code is so
3166 // fragile. Do nothing, because we don't want to break the print job currently in progress, and
3167 // the print job currently in progress is responsible for its own cleanup.
3168 ASSERT_NOT_REACHED();
3170 // The print job that kicked off this delayed call has finished, and this view is not being
3171 // printed again. We expect that no other print job has started. Since this delayed call wasn't
3172 // cancelled, beginDocument and endDocument must not have been called, and we need to clean up
3173 // the print mode here.
3174 ASSERT(currentOperation == nil);
3175 [self _endPrintMode];
3179 // Return the number of pages available for printing
3180 - (BOOL)knowsPageRange:(NSRangePointer)range
3182 // Must do this explicit display here, because otherwise the view might redisplay while the print
3183 // sheet was up, using printer fonts (and looking different).
3184 [self displayIfNeeded];
3185 [[self window] setAutodisplay:NO];
3187 // If we are a frameset just print with the layout we have onscreen, otherwise relayout
3188 // according to the paper size
3189 float minLayoutWidth = 0.0f;
3190 float maxLayoutWidth = 0.0f;
3191 if (![[self _bridge] isFrameSet]) {
3192 float paperWidth = [self _availablePaperWidthForPrintOperation:[NSPrintOperation currentOperation]];
3193 minLayoutWidth = paperWidth * PrintingMinimumShrinkFactor;
3194 maxLayoutWidth = paperWidth * PrintingMaximumShrinkFactor;
3196 [self _setPrinting:YES minimumPageWidth:minLayoutWidth maximumPageWidth:maxLayoutWidth adjustViewSize:YES]; // will relayout
3197 NSPrintOperation *printOperation = [NSPrintOperation currentOperation];
3198 // Certain types of errors, including invalid page ranges, can cause beginDocument and
3199 // endDocument to be skipped after we've put ourselves in print mode (see 4145905). In those cases
3200 // we need to get out of print mode without relying on any more callbacks from the printing mechanism.
3201 // If we get as far as beginDocument without trouble, then this delayed request will be cancelled.
3202 // If not cancelled, this delayed call will be invoked in the next pass through the main event loop,
3203 // which is after beginDocument and endDocument would be called.
3204 [self performSelector:@selector(_delayedEndPrintMode:) withObject:printOperation afterDelay:0];
3205 [[self _webView] _adjustPrintingMarginsForHeaderAndFooter];
3207 // There is a theoretical chance that someone could do some drawing between here and endDocument,
3208 // if something caused setNeedsDisplay after this point. If so, it's not a big tragedy, because
3209 // you'd simply see the printer fonts on screen. As of this writing, this does not happen with Safari.
3211 range->location = 1;
3212 float totalScaleFactor = [self _scaleFactorForPrintOperation:printOperation];
3213 float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
3214 [_private->pageRects release];
3215 float fullPageHeight = floorf([self _calculatePrintHeight]/totalScaleFactor);
3216 NSArray *newPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor
3217 printHeight:fullPageHeight];
3219 // AppKit gets all messed up if you give it a zero-length page count (see 3576334), so if we
3220 // hit that case we'll pass along a degenerate 1 pixel square to print. This will print
3221 // a blank page (with correct-looking header and footer if that option is on), which matches
3222 // the behavior of IE and Camino at least.
3223 if ([newPageRects count] == 0)
3224 newPageRects = [NSArray arrayWithObject:[NSValue valueWithRect:NSMakeRect(0, 0, 1, 1)]];
3225 else if ([newPageRects count] > 1) {
3226 // If the last page is a short orphan, try adjusting the print height slightly to see if this will squeeze the
3227 // content onto one fewer page. If it does, use the adjusted scale. If not, use the original scale.
3228 float lastPageHeight = NSHeight([[newPageRects lastObject] rectValue]);
3229 if (lastPageHeight/fullPageHeight < LastPrintedPageOrphanRatio) {
3230 NSArray *adjustedPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor
3231 printHeight:fullPageHeight*PrintingOrphanShrinkAdjustment];
3232 // Use the adjusted rects only if the page count went down
3233 if ([adjustedPageRects count] < [newPageRects count])
3234 newPageRects = adjustedPageRects;
3238 _private->pageRects = [newPageRects retain];
3240 range->length = [_private->pageRects count];
3245 // Return the drawing rectangle for a particular page number
3246 - (NSRect)rectForPage:(int)page
3248 return [[_private->pageRects objectAtIndex:page - 1] rectValue];
3251 - (void)drawPageBorderWithSize:(NSSize)borderSize
3253 ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize]));
3254 [[self _webView] _drawHeaderAndFooter];
3257 - (void)beginDocument
3260 // From now on we'll get a chance to call _endPrintMode in either beginDocument or
3261 // endDocument, so we can cancel the "just in case" pending call.
3262 [NSObject cancelPreviousPerformRequestsWithTarget:self
3263 selector:@selector(_delayedEndPrintMode:)
3264 object:[NSPrintOperation currentOperation]];
3265 [super beginDocument];
3267 // Exception during [super beginDocument] means that endDocument will not get called,
3268 // so we need to clean up our "print mode" here.
3269 [self _endPrintMode];
3275 [super endDocument];
3276 // Note sadly at this point [NSGraphicsContext currentContextDrawingToScreen] is still NO
3277 [self _endPrintMode];
3280 - (BOOL)_interceptEditingKeyEvent:(NSEvent *)event
3282 // Use WebView's tabKeyCyclesThroughElements state to determine whether or not
3283 // to process tab key events. The idea here is that tabKeyCyclesThroughElements
3284 // will be YES when this WebView is being used in a browser, and we desire the
3285 // behavior where tab moves to the next element in tab order. If tabKeyCyclesThroughElements
3286 // is NO, it is likely that the WebView is being embedded as the whole view, as in Mail,
3287 // and tabs should input tabs as expected in a text editor. Using Option-Tab always cycles
3288 // through elements.
3290 if ([[self _webView] tabKeyCyclesThroughElements] && [event _web_isTabKeyEvent])
3293 if (![[self _webView] tabKeyCyclesThroughElements] && [event _web_isOptionTabKeyEvent])
3296 // Now process the key normally
3297 [self interpretKeyEvents:[NSArray arrayWithObject:event]];
3301 - (void)keyDown:(NSEvent *)event
3305 BOOL callSuper = NO;
3307 _private->keyDownEvent = event;
3309 WebFrameBridge *bridge = [self _bridge];
3310 if ([bridge interceptKeyEvent:event toView:self]) {
3311 // WebCore processed a key event, bail on any outstanding complete: UI
3312 [_private->compController endRevertingChange:YES moveLeft:NO];
3313 } else if (_private->compController && [_private->compController filterKeyDown:event]) {
3314 // Consumed by complete: popup window
3316 // We're going to process a key event, bail on any outstanding complete: UI
3317 [_private->compController endRevertingChange:YES moveLeft:NO];
3318 BOOL handledKey = [self _canEdit] && [self _interceptEditingKeyEvent:event];
3323 [super keyDown:event];
3325 [NSCursor setHiddenUntilMouseMoves:YES];
3327 _private->keyDownEvent = nil;
3332 - (void)keyUp:(NSEvent *)event
3335 if (![[self _bridge] interceptKeyEvent:event toView:self])
3336 [super keyUp:event];
3340 - (id)accessibilityAttributeValue:(NSString*)attributeName
3342 if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) {
3343 id accTree = [[self _bridge] accessibilityTree];
3345 return [NSArray arrayWithObject:accTree];
3348 return [super accessibilityAttributeValue:attributeName];
3351 - (id)accessibilityFocusedUIElement
3353 id accTree = [[self _bridge] accessibilityTree];
3355 return [accTree accessibilityFocusedUIElement];
3359 - (id)accessibilityHitTest:(NSPoint)point
3361 id accTree = [[self _bridge] accessibilityTree];
3363 NSPoint windowCoord = [[self window] convertScreenToBase:point];
3364 return [accTree accessibilityHitTest:[self convertPoint:windowCoord fromView:nil]];
3369 - (id)_accessibilityParentForSubview:(NSView *)subview
3371 id accTree = [[self _bridge] accessibilityTree];
3374 id parent = [accTree _accessibilityParentForSubview:subview];
3380 - (void)centerSelectionInVisibleArea:(id)sender
3382 [[self _bridge] centerSelectionInVisibleArea];
3385 - (void)moveBackward:(id)sender
3387 if ([self _canAlterCurrentSelection])
3388 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectBackward granularity:WebBridgeSelectByCharacter];
3391 - (void)moveBackwardAndModifySelection:(id)sender
3393 if ([self _canAlterCurrentSelection])
3394 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectBackward granularity:WebBridgeSelectByCharacter];
3397 - (void)moveDown:(id)sender
3399 if ([self _canAlterCurrentSelection])
3400 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectForward granularity:WebBridgeSelectByLine];
3403 - (void)moveDownAndModifySelection:(id)sender
3405 if ([self _canAlterCurrentSelection])
3406 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectForward granularity:WebBridgeSelectByLine];
3409 - (void)moveForward:(id)sender
3411 if ([self _canAlterCurrentSelection])
3412 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectForward granularity:WebBridgeSelectByCharacter];
3415 - (void)moveForwardAndModifySelection:(id)sender
3417 if ([self _canAlterCurrentSelection])
3418 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectForward granularity:WebBridgeSelectByCharacter];
3421 - (void)moveLeft:(id)sender
3423 if ([self _canAlterCurrentSelection])
3424 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectLeft granularity:WebBridgeSelectByCharacter];
3427 - (void)moveLeftAndModifySelection:(id)sender
3429 if ([self _canAlterCurrentSelection])
3430 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectLeft granularity:WebBridgeSelectByCharacter];
3433 - (void)moveRight:(id)sender
3435 if ([self _canAlterCurrentSelection])
3436 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectRight granularity:WebBridgeSelectByCharacter];
3439 - (void)moveRightAndModifySelection:(id)sender
3441 if ([self _canAlterCurrentSelection])
3442 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectRight granularity:WebBridgeSelectByCharacter];
3445 - (void)moveToBeginningOfDocument:(id)sender
3447 if ([self _canAlterCurrentSelection])
3448 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectBackward granularity:WebBridgeSelectToDocumentBoundary];
3451 - (void)moveToBeginningOfDocumentAndModifySelection:(id)sender
3453 if ([self _canAlterCurrentSelection])
3454 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectBackward granularity:WebBridgeSelectToDocumentBoundary];
3457 - (void)moveToBeginningOfSentence:(id)sender
3459 if ([self _canAlterCurrentSelection])
3460 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectBackward granularity:WebBridgeSelectToSentenceBoundary];
3463 - (void)moveToBeginningOfSentenceAndModifySelection:(id)sender
3465 if ([self _canAlterCurrentSelection])
3466 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectBackward granularity:WebBridgeSelectToSentenceBoundary];
3469 - (void)moveToBeginningOfLine:(id)sender
3471 if ([self _canAlterCurrentSelection])
3472 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectBackward granularity:WebBridgeSelectToLineBoundary];
3475 - (void)moveToBeginningOfLineAndModifySelection:(id)sender
3477 if ([self _canAlterCurrentSelection])
3478 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectBackward granularity:WebBridgeSelectToLineBoundary];
3481 - (void)moveToBeginningOfParagraph:(id)sender
3483 if ([self _canAlterCurrentSelection])
3484 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectBackward granularity:WebBridgeSelectToParagraphBoundary];
3487 - (void)moveToBeginningOfParagraphAndModifySelection:(id)sender
3489 if ([self _canAlterCurrentSelection])
3490 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectBackward granularity:WebBridgeSelectToParagraphBoundary];
3493 - (void)moveToEndOfDocument:(id)sender
3495 if ([self _canAlterCurrentSelection])
3496 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectForward granularity:WebBridgeSelectToDocumentBoundary];
3499 - (void)moveToEndOfDocumentAndModifySelection:(id)sender
3501 if ([self _canAlterCurrentSelection])
3502 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectForward granularity:WebBridgeSelectToDocumentBoundary];
3505 - (void)moveToEndOfSentence:(id)sender
3507 if ([self _canAlterCurrentSelection])
3508 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectForward granularity:WebBridgeSelectToSentenceBoundary];
3511 - (void)moveToEndOfSentenceAndModifySelection:(id)sender
3513 if ([self _canAlterCurrentSelection])
3514 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectForward granularity:WebBridgeSelectToSentenceBoundary];
3517 - (void)moveToEndOfLine:(id)sender
3519 if ([self _canAlterCurrentSelection])
3520 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectForward granularity:WebBridgeSelectToLineBoundary];
3523 - (void)moveToEndOfLineAndModifySelection:(id)sender
3525 if ([self _canAlterCurrentSelection])
3526 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectForward granularity:WebBridgeSelectToLineBoundary];
3529 - (void)moveToEndOfParagraph:(id)sender
3531 if ([self _canAlterCurrentSelection])
3532 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectForward granularity:WebBridgeSelectToParagraphBoundary];
3535 - (void)moveToEndOfParagraphAndModifySelection:(id)sender
3537 if ([self _canAlterCurrentSelection])
3538 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectForward granularity:WebBridgeSelectToParagraphBoundary];
3541 - (void)moveParagraphBackwardAndModifySelection:(id)sender
3543 if ([self _canAlterCurrentSelection])
3544 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectBackward granularity:WebBridgeSelectByParagraph];
3547 - (void)moveParagraphForwardAndModifySelection:(id)sender
3549 if ([self _canAlterCurrentSelection])
3550 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectForward granularity:WebBridgeSelectByParagraph];
3553 - (void)moveUp:(id)sender
3555 if ([self _canAlterCurrentSelection])
3556 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectBackward granularity:WebBridgeSelectByLine];
3559 - (void)moveUpAndModifySelection:(id)sender
3561 if ([self _canAlterCurrentSelection])
3562 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectBackward granularity:WebBridgeSelectByLine];
3565 - (void)moveWordBackward:(id)sender
3567 if ([self _canAlterCurrentSelection])
3568 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectBackward granularity:WebBridgeSelectByWord];
3571 - (void)moveWordBackwardAndModifySelection:(id)sender
3573 if ([self _canAlterCurrentSelection])
3574 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectBackward granularity:WebBridgeSelectByWord];
3577 - (void)moveWordForward:(id)sender
3579 if ([self _canAlterCurrentSelection])
3580 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectForward granularity:WebBridgeSelectByWord];
3583 - (void)moveWordForwardAndModifySelection:(id)sender
3585 if ([self _canAlterCurrentSelection])
3586 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectForward granularity:WebBridgeSelectByWord];
3589 - (void)moveWordLeft:(id)sender
3591 if ([self _canAlterCurrentSelection])
3592 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectLeft granularity:WebBridgeSelectByWord];
3595 - (void)moveWordLeftAndModifySelection:(id)sender
3597 if ([self _canAlterCurrentSelection])
3598 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectLeft granularity:WebBridgeSelectByWord];
3601 - (void)moveWordRight:(id)sender
3603 if ([self _canAlterCurrentSelection])
3604 [[self _bridge] alterCurrentSelection:WebSelectByMoving direction:WebBridgeSelectRight granularity:WebBridgeSelectByWord];
3607 - (void)moveWordRightAndModifySelection:(id)sender
3609 if ([self _canAlterCurrentSelection])
3610 [[self _bridge] alterCurrentSelection:WebSelectByExtending direction:WebBridgeSelectRight granularity:WebBridgeSelectByWord];
3613 - (void)pageUp:(id)sender
3615 WebFrameView *frameView = [self _frameView];
3618 if ([self _canAlterCurrentSelection])
3619 [[self _bridge] alterCurrentSelection:WebSelectByMoving verticalDistance:-[frameView _verticalPageScrollDistance]];
3622 - (void)pageDown:(id)sender
3624 WebFrameView *frameView = [self _frameView];
3627 if ([self _canAlterCurrentSelection])
3628 [[self _bridge] alterCurrentSelection:WebSelectByMoving verticalDistance:[frameView _verticalPageScrollDistance]];
3631 - (void)pageUpAndModifySelection:(id)sender
3633 WebFrameView *frameView = [self _frameView];
3636 if ([self _canAlterCurrentSelection])
3637 [[self _bridge] alterCurrentSelection:WebSelectByExtending verticalDistance:-[frameView _verticalPageScrollDistance]];
3640 - (void)pageDownAndModifySelection:(id)sender
3642 WebFrameView *frameView = [self _frameView];
3643 if (frameView == nil)
3645 if ([self _canAlterCurrentSelection])
3646 [[self _bridge] alterCurrentSelection:WebSelectByExtending verticalDistance:[frameView _verticalPageScrollDistance]];
3649 - (void)_expandSelectionToGranularity:(WebBridgeSelectionGranularity)granularity
3651 if (![self _canAlterCurrentSelection])
3654 WebFrameBridge *bridge = [self _bridge];
3655 DOMRange *range = [bridge rangeByExpandingSelectionWithGranularity:granularity];
3656 if (range && ![range collapsed]) {
3657 WebView *webView = [self _webView];
3658 if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:range affinity:[bridge selectionAffinity] stillSelecting:NO])
3659 [bridge setSelectedDOMRange:range affinity:[bridge selectionAffinity] closeTyping:YES];
3663 - (void)selectParagraph:(id)sender
3665 [self _expandSelectionToGranularity:WebBridgeSelectByParagraph];
3668 - (void)selectLine:(id)sender
3670 [self _expandSelectionToGranularity:WebBridgeSelectByLine];
3673 - (void)selectSentence:(id)sender
3675 [self _expandSelectionToGranularity:WebBridgeSelectBySentence];
3678 - (void)selectWord:(id)sender
3680 [self _expandSelectionToGranularity:WebBridgeSelectByWord];
3683 - (void)copy:(id)sender
3685 if ([[self _bridge] tryDHTMLCopy])
3686 return; // DHTML did the whole operation
3687 if (![self _canCopy]) {
3691 [self _writeSelectionToPasteboard:[NSPasteboard generalPasteboard]];
3694 - (void)delete:(id)sender
3696 if (![self _canDelete]) {
3700 [self _deleteSelection];
3703 - (void)cut:(id)sender
3705 WebFrameBridge *bridge = [self _bridge];
3706 if ([bridge tryDHTMLCut])
3707 return; // DHTML did the whole operation
3708 if (![self _canCut]) {
3712 DOMRange *range = [self _selectedRange];
3713 if ([self _shouldDeleteRange:range]) {
3714 [self _writeSelectionToPasteboard:[NSPasteboard generalPasteboard]];
3715 [bridge deleteSelectionWithSmartDelete:[self _canSmartCopyOrDelete]];
3719 - (void)paste:(id)sender
3721 if ([[self _bridge] tryDHTMLPaste])
3722 return; // DHTML did the whole operation
3723 if (![self _canPaste])
3725 if ([[self _bridge] isSelectionRichlyEditable])
3726 [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:YES];
3728 [self _pasteAsPlainTextWithPasteboard:[NSPasteboard generalPasteboard]];
3731 - (NSData *)_selectionStartFontAttributesAsRTF
3733 NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"x"
3734 attributes:[[self _bridge] fontAttributesForSelectionStart]];
3735 NSData *data = [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:nil];
3740 - (NSDictionary *)_fontAttributesFromFontPasteboard
3742 NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
3743 if (fontPasteboard == nil)
3745 NSData *data = [fontPasteboard dataForType:NSFontPboardType];
3746 if (data == nil || [data length] == 0)
3748 // NSTextView does something more efficient by parsing the attributes only, but that's not available in API.
3749 NSAttributedString *string = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease];
3750 if (string == nil || [string length] == 0)
3752 return [string fontAttributesInRange:NSMakeRange(0, 1)];
3755 - (DOMCSSStyleDeclaration *)_emptyStyle
3757 return [[[self _bridge] DOMDocument] createCSSStyleDeclaration];
3760 - (NSString *)_colorAsString:(NSColor *)color
3762 NSColor *rgbColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
3763 // FIXME: If color is non-nil and rgbColor is nil, that means we got some kind
3764 // of fancy color that can't be converted to RGB. Changing that to "transparent"
3765 // might not be great, but it's probably OK.
3766 if (rgbColor == nil)
3767 return @"transparent";
3768 float r = [rgbColor redComponent];
3769 float g = [rgbColor greenComponent];
3770 float b = [rgbColor blueComponent];
3771 float a = [rgbColor alphaComponent];
3773 return @"transparent";
3774 if (r == 0 && g == 0 && b == 0 && a == 1)
3776 if (r == 1 && g == 1 && b == 1 && a == 1)
3778 // FIXME: Lots more named colors. Maybe we could use the table in WebCore?
3780 return [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", r * 255, g * 255, b * 255];
3781 return [NSString stringWithFormat:@"rgba(%.0f,%.0f,%.0f,%f)", r * 255, g * 255, b * 255, a];
3784 - (NSString *)_shadowAsString:(NSShadow *)shadow
3788 NSSize offset = [shadow shadowOffset];
3789 float blurRadius = [shadow shadowBlurRadius];
3790 if (offset.width == 0 && offset.height == 0 && blurRadius == 0)
3792 NSColor *color = [shadow shadowColor];
3795 // FIXME: Handle non-integral values here?
3796 if (blurRadius == 0)
3797 return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height];
3798 return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height, blurRadius];
3801 - (DOMCSSStyleDeclaration *)_styleFromFontAttributes:(NSDictionary *)dictionary
3803 DOMCSSStyleDeclaration *style = [self _emptyStyle];
3805 NSColor *color = [dictionary objectForKey:NSBackgroundColorAttributeName];
3806 [style setBackgroundColor:[self _colorAsString:color]];
3808 NSFont *font = [dictionary objectForKey:NSFontAttributeName];
3810 [style setFontFamily:@"Helvetica"];
3811 [style setFontSize:@"12px"];
3812 [style setFontWeight:@"normal"];
3813 [style setFontStyle:@"normal"];
3815 NSFontManager *fm = [NSFontManager sharedFontManager];
3816 // FIXME: Need more sophisticated escaping code if we want to handle family names
3817 // with characters like single quote or backslash in their names.
3818 [style setFontFamily:[NSString stringWithFormat:@"'%@'", [font familyName]]];
3819 [style setFontSize:[NSString stringWithFormat:@"%0.fpx", [font pointSize]]];
3820 if ([fm weightOfFont:font] >= MIN_BOLD_WEIGHT)
3821 [style setFontWeight:@"bold"];
3823 [style setFontWeight:@"normal"];
3824 if (([fm traitsOfFont:font] & NSItalicFontMask) != 0)
3825 [style setFontStyle:@"italic"];
3827 [style setFontStyle:@"normal"];
3830 color = [dictionary objectForKey:NSForegroundColorAttributeName];
3831 [style setColor:color ? [self _colorAsString:color] : (NSString *)@"black"];
3833 NSShadow *shadow = [dictionary objectForKey:NSShadowAttributeName];
3834 [style setTextShadow:[self _shadowAsString:shadow]];
3836 int strikethroughInt = [[dictionary objectForKey:NSStrikethroughStyleAttributeName] intValue];
3838 int superscriptInt = [[dictionary objectForKey:NSSuperscriptAttributeName] intValue];
3839 if (superscriptInt > 0)
3840 [style setVerticalAlign:@"super"];
3841 else if (superscriptInt < 0)
3842 [style setVerticalAlign:@"sub"];
3844 [style setVerticalAlign:@"baseline"];
3845 int underlineInt = [[dictionary objectForKey:NSUnderlineStyleAttributeName] intValue];
3846 // FIXME: Underline wins here if we have both (see bug 3790443).
3847 if (strikethroughInt == NSUnderlineStyleNone && underlineInt == NSUnderlineStyleNone)
3848 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""];
3849 else if (underlineInt == NSUnderlineStyleNone)
3850 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""];
3852 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""];
3857 - (void)_applyStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(WebUndoAction)undoAction
3859 if (style == nil || [style length] == 0 || ![self _canEditRichly])
3861 WebView *webView = [self _webView];
3862 WebFrameBridge *bridge = [self _bridge];
3863 if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:[self _selectedRange]]) {
3864 [bridge applyStyle:style withUndoAction:undoAction];
3868 - (void)_applyParagraphStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(WebUndoAction)undoAction
3870 if (style == nil || [style length] == 0 || ![self _canEditRichly])
3872 WebView *webView = [self _webView];
3873 WebFrameBridge *bridge = [self _bridge];
3874 if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:[self _selectedRange]])
3875 [bridge applyParagraphStyle:style withUndoAction:undoAction];
3880 DOMCSSStyleDeclaration *style = [self _emptyStyle];
3881 [style setFontWeight:@"bold"];
3882 if ([[self _bridge] selectionStartHasStyle:style])
3883 [style setFontWeight:@"normal"];
3884 [self _applyStyleToSelection:style withUndoAction:WebUndoActionSetFont];
3887 - (void)_toggleItalic
3889 DOMCSSStyleDeclaration *style = [self _emptyStyle];
3890 [style setFontStyle:@"italic"];
3891 if ([[self _bridge] selectionStartHasStyle:style])
3892 [style setFontStyle:@"normal"];
3893 [self _applyStyleToSelection:style withUndoAction:WebUndoActionSetFont];
3896 - (BOOL)_handleStyleKeyEquivalent:(NSEvent *)event
3898 ASSERT([self _webView]);
3899 if (![[[self _webView] preferences] respectStandardStyleKeyEquivalents])
3902 if (![self _canEdit])
3905 NSString *string = [event charactersIgnoringModifiers];
3906 if ([string isEqualToString:@"b"]) {
3910 if ([string isEqualToString:@"i"]) {
3911 [self _toggleItalic];
3918 - (BOOL)performKeyEquivalent:(NSEvent *)event
3920 if ([self _handleStyleKeyEquivalent:event])
3927 // Pass command-key combos through WebCore if there is a key binding available for
3928 // this event. This lets web pages have a crack at intercepting command-modified keypresses.
3929 // But don't do it if we have already handled the event.
3930 if (event != _private->keyDownEvent
3931 && [self _web_firstResponderIsSelfOrDescendantView]
3932 && [[self _bridge] interceptKeyEvent:event toView:self])
3935 ret = [super performKeyEquivalent:event];
3942 - (void)copyFont:(id)sender
3944 // Put RTF with font attributes on the pasteboard.
3945 // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
3946 NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
3947 [fontPasteboard declareTypes:[NSArray arrayWithObject:NSFontPboardType] owner:nil];
3948 [fontPasteboard setData:[self _selectionStartFontAttributesAsRTF] forType:NSFontPboardType];
3951 - (void)pasteFont:(id)sender
3953 // Read RTF with font attributes from the pasteboard.
3954 // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
3955 [self _applyStyleToSelection:[self _styleFromFontAttributes:[self _fontAttributesFromFontPasteboard]] withUndoAction:WebUndoActionPasteFont];
3958 - (void)pasteAsPlainText:(id)sender
3960 if (![self _canEdit])
3962 [self _pasteAsPlainTextWithPasteboard:[NSPasteboard generalPasteboard]];
3965 - (void)pasteAsRichText:(id)sender
3967 // Since rich text always beats plain text when both are on the pasteboard, it's not
3968 // clear how this is different from plain old paste.
3969 [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO];
3972 - (NSFont *)_originalFontA
3974 return [[NSFontManager sharedFontManager] fontWithFamily:@"Helvetica" traits:0 weight:STANDARD_WEIGHT size:10.0f];
3977 - (NSFont *)_originalFontB
3979 return [[NSFontManager sharedFontManager] fontWithFamily:@"Times" traits:(NSBoldFontMask | NSItalicFontMask) weight:STANDARD_BOLD_WEIGHT size:12.0f];
3982 - (void)_addToStyle:(DOMCSSStyleDeclaration *)style fontA:(NSFont *)a fontB:(NSFont *)b
3984 // Since there's no way to directly ask NSFontManager what style change it's going to do
3985 // we instead pass two "specimen" fonts to it and let it change them. We then deduce what
3986 // style change it was doing by looking at what happened to each of the two fonts.
3987 // So if it was making the text bold, both fonts will be bold after the fact.
3989 if (a == nil || b == nil)
3992 NSFontManager *fm = [NSFontManager sharedFontManager];
3994 NSFont *oa = [self _originalFontA];
3996 NSString *aFamilyName = [a familyName];
3997 NSString *bFamilyName = [b familyName];
3999 int aPointSize = [a pointSize];
4000 int bPointSize = [b pointSize];
4002 int aWeight = [fm weightOfFont:a];
4003 int bWeight = [fm weightOfFont:b];
4005 BOOL aIsBold = aWeight >= MIN_BOLD_WEIGHT;
4007 BOOL aIsItalic = ([fm traitsOfFont:a] & NSItalicFontMask) != 0;
4008 BOOL bIsItalic = ([fm traitsOfFont:b] & NSItalicFontMask) != 0;
4010 if ([aFamilyName isEqualToString:bFamilyName]) {
4011 NSString *familyNameForCSS = aFamilyName;
4013 // The family name may not be specific enough to get us the font specified.
4014 // In some cases, the only way to get exactly what we are looking for is to use
4015 // the Postscript name.
4017 // Find the font the same way the rendering code would later if it encountered this CSS.
4018 NSFontTraitMask traits = 0;
4020 traits |= NSBoldFontMask;
4022 traits |= NSItalicFontMask;
4023 NSFont *foundFont = WebCoreFindFont(aFamilyName, traits, aPointSize);
4025 // If we don't find a font with the same Postscript name, then we'll have to use the
4026 // Postscript name to make the CSS specific enough.
4027 if (![[foundFont fontName] isEqualToString:[a fontName]]) {
4028 familyNameForCSS = [a fontName];
4031 // FIXME: Need more sophisticated escaping code if we want to handle family names
4032 // with characters like single quote or backslash in their names.
4033 [style setFontFamily:[NSString stringWithFormat:@"'%@'", familyNameForCSS]];
4036 int soa = [oa pointSize];
4037 if (aPointSize == bPointSize)
4038 [style setFontSize:[NSString stringWithFormat:@"%dpx", aPointSize]];
4039 else if (aPointSize < soa)
4040 [style _setFontSizeDelta:@"-1px"];
4041 else if (aPointSize > soa)
4042 [style _setFontSizeDelta:@"1px"];
4044 if (aWeight == bWeight)
4045 [style setFontWeight:aIsBold ? @"bold" : @"normal"];
4047 if (aIsItalic == bIsItalic)
4048 [style setFontStyle:aIsItalic ? @"italic" : @"normal"];
4051 - (DOMCSSStyleDeclaration *)_styleFromFontManagerOperation
4053 DOMCSSStyleDeclaration *style = [self _emptyStyle];
4055 NSFontManager *fm = [NSFontManager sharedFontManager];
4057 NSFont *oa = [self _originalFontA];
4058 NSFont *ob = [self _originalFontB];
4059 [self _addToStyle:style fontA:[fm convertFont:oa] fontB:[fm convertFont:ob]];
4064 - (void)changeFont:(id)sender
4066 [self _applyStyleToSelection:[self _styleFromFontManagerOperation] withUndoAction:WebUndoActionSetFont];
4069 - (DOMCSSStyleDeclaration *)_styleForAttributeChange:(id)sender
4071 DOMCSSStyleDeclaration *style = [self _emptyStyle];
4073 NSShadow *shadow = [[NSShadow alloc] init];
4074 [shadow setShadowOffset:NSMakeSize(1, 1)];
4076 NSDictionary *oa = [NSDictionary dictionaryWithObjectsAndKeys:
4077 [self _originalFontA], NSFontAttributeName,
4079 NSDictionary *ob = [NSDictionary dictionaryWithObjectsAndKeys:
4080 [NSColor blackColor], NSBackgroundColorAttributeName,
4081 [self _originalFontB], NSFontAttributeName,
4082 [NSColor whiteColor], NSForegroundColorAttributeName,
4083 shadow, NSShadowAttributeName,
4084 [NSNumber numberWithInt:NSUnderlineStyleSingle], NSStrikethroughStyleAttributeName,
4085 [NSNumber numberWithInt:1], NSSuperscriptAttributeName,
4086 [NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName,
4093 NSObliquenessAttributeName /* float; skew to be applied to glyphs, default 0: no skew */
4094 // font-style, but that is just an on-off switch
4096 NSExpansionAttributeName /* float; log of expansion factor to be applied to glyphs, default 0: no expansion */
4099 NSKernAttributeName /* float, amount to modify default kerning, if 0, kerning off */
4100 // letter-spacing? probably not good enough
4102 NSUnderlineColorAttributeName /* NSColor, default nil: same as foreground color */
4103 NSStrikethroughColorAttributeName /* NSColor, default nil: same as foreground color */
4104 // text-decoration-color?
4106 NSLigatureAttributeName /* int, default 1: default ligatures, 0: no ligatures, 2: all ligatures */
4107 NSBaselineOffsetAttributeName /* float, in points; offset from baseline, default 0 */
4108 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) */
4109 NSStrokeColorAttributeName /* NSColor, default nil: same as foreground color */
4114 NSDictionary *a = [sender convertAttributes:oa];
4115 NSDictionary *b = [sender convertAttributes:ob];
4117 NSColor *ca = [a objectForKey:NSBackgroundColorAttributeName];
4118 NSColor *cb = [b objectForKey:NSBackgroundColorAttributeName];
4120 [style setBackgroundColor:[self _colorAsString:ca]];
4123 [self _addToStyle:style fontA:[a objectForKey:NSFontAttributeName] fontB:[b objectForKey:NSFontAttributeName]];
4125 ca = [a objectForKey:NSForegroundColorAttributeName];
4126 cb = [b objectForKey:NSForegroundColorAttributeName];
4128 [style setColor:[self _colorAsString:ca]];
4131 NSShadow *sha = [a objectForKey:NSShadowAttributeName];
4133 [style setTextShadow:[self _shadowAsString:sha]];
4134 else if ([b objectForKey:NSShadowAttributeName] == nil)
4135 [style setTextShadow:@"none"];
4137 int sa = [[a objectForKey:NSStrikethroughStyleAttributeName] intValue];
4138 int sb = [[b objectForKey:NSStrikethroughStyleAttributeName] intValue];
4140 if (sa == NSUnderlineStyleNone)
4141 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""];
4142 // we really mean "no line-through" rather than "none"
4144 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""];