diff -r 000000000000 -r dd21522fd290 webengine/osswebengine/WebKit/WebView/WebView.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webengine/osswebengine/WebKit/WebView/WebView.mm Mon Mar 30 12:54:55 2009 +0300 @@ -0,0 +1,4576 @@ +/* + * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2006 David Smith (catfish.man@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "WebViewInternal.h" + +#import "DOMRangeInternal.h" +#import "WebBackForwardList.h" +#import "WebBackForwardListInternal.h" +#import "WebBaseNetscapePluginView.h" +#import "WebChromeClient.h" +#import "WebContextMenuClient.h" +#import "WebDOMOperationsPrivate.h" +#import "WebDashboardRegion.h" +#import "WebDataSourceInternal.h" +#import "WebDefaultEditingDelegate.h" +#import "WebDefaultPolicyDelegate.h" +#import "WebDefaultScriptDebugDelegate.h" +#import "WebDefaultUIDelegate.h" +#import "WebDocument.h" +#import "WebDocumentInternal.h" +#import "WebDownload.h" +#import "WebDownloadInternal.h" +#import "WebDragClient.h" +#import "WebDynamicScrollBarsView.h" +#import "WebEditingDelegate.h" +#import "WebEditorClient.h" +#import "WebFormDelegatePrivate.h" +#import "WebFrameBridge.h" +#import "WebFrameInternal.h" +#import "WebFrameViewInternal.h" +#import "WebHTMLRepresentation.h" +#import "WebHTMLViewInternal.h" +#import "WebHistoryItemInternal.h" +#import "WebIconDatabase.h" +#import "WebIconDatabaseInternal.h" +#import "WebInspectorClient.h" +#import "WebKitErrors.h" +#import "WebKitLogging.h" +#import "WebKitNSStringExtras.h" +#import "WebKitStatisticsPrivate.h" +#import "WebKitSystemBits.h" +#import "WebKitVersionChecks.h" +#import "WebLocalizableStrings.h" +#import "WebNSDataExtras.h" +#import "WebNSDataExtrasPrivate.h" +#import "WebNSDictionaryExtras.h" +#import "WebNSEventExtras.h" +#import "WebNSObjectExtras.h" +#import "WebNSPasteboardExtras.h" +#import "WebNSPrintOperationExtras.h" +#import "WebNSURLExtras.h" +#import "WebNSURLRequestExtras.h" +#import "WebNSUserDefaultsExtras.h" +#import "WebNSViewExtras.h" +#import "WebPanelAuthenticationHandler.h" +#import "WebPasteboardHelper.h" +#import "WebPDFView.h" +#import "WebPluginDatabase.h" +#import "WebPolicyDelegate.h" +#import "WebPreferenceKeysPrivate.h" +#import "WebPreferencesPrivate.h" +#import "WebScriptDebugDelegatePrivate.h" +#import "WebScriptDebugServerPrivate.h" +#import "WebUIDelegate.h" +#import "WebUIDelegatePrivate.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +using namespace WebCore; + +#if defined(__ppc__) || defined(__ppc64__) +#define PROCESSOR "PPC" +#elif defined(__i386__) || defined(__x86_64__) +#define PROCESSOR "Intel" +#else +#error Unknown architecture +#endif + +#define FOR_EACH_RESPONDER_SELECTOR(macro) \ +macro(alignCenter) \ +macro(alignJustified) \ +macro(alignLeft) \ +macro(alignRight) \ +macro(capitalizeWord) \ +macro(centerSelectionInVisibleArea) \ +macro(changeAttributes) \ +macro(changeColor) \ +macro(changeDocumentBackgroundColor) \ +macro(changeFont) \ +macro(checkSpelling) \ +macro(complete) \ +macro(copy) \ +macro(copyFont) \ +macro(cut) \ +macro(delete) \ +macro(deleteBackward) \ +macro(deleteBackwardByDecomposingPreviousCharacter) \ +macro(deleteForward) \ +macro(deleteToBeginningOfLine) \ +macro(deleteToBeginningOfParagraph) \ +macro(deleteToEndOfLine) \ +macro(deleteToEndOfParagraph) \ +macro(deleteWordBackward) \ +macro(deleteWordForward) \ +macro(ignoreSpelling) \ +macro(indent) \ +macro(insertBacktab) \ +macro(insertNewline) \ +macro(insertNewlineIgnoringFieldEditor) \ +macro(insertParagraphSeparator) \ +macro(insertTab) \ +macro(insertTabIgnoringFieldEditor) \ +macro(lowercaseWord) \ +macro(moveBackward) \ +macro(moveBackwardAndModifySelection) \ +macro(moveDown) \ +macro(moveDownAndModifySelection) \ +macro(moveForward) \ +macro(moveForwardAndModifySelection) \ +macro(moveLeft) \ +macro(moveLeftAndModifySelection) \ +macro(moveRight) \ +macro(moveRightAndModifySelection) \ +macro(moveToBeginningOfDocument) \ +macro(moveToBeginningOfDocumentAndModifySelection) \ +macro(moveToBeginningOfSentence) \ +macro(moveToBeginningOfSentenceAndModifySelection) \ +macro(moveToBeginningOfLine) \ +macro(moveToBeginningOfLineAndModifySelection) \ +macro(moveToBeginningOfParagraph) \ +macro(moveToBeginningOfParagraphAndModifySelection) \ +macro(moveToEndOfDocument) \ +macro(moveToEndOfDocumentAndModifySelection) \ +macro(moveToEndOfLine) \ +macro(moveToEndOfLineAndModifySelection) \ +macro(moveToEndOfParagraph) \ +macro(moveToEndOfParagraphAndModifySelection) \ +macro(moveToEndOfSentence) \ +macro(moveToEndOfSentenceAndModifySelection) \ +macro(moveUp) \ +macro(moveUpAndModifySelection) \ +macro(moveWordBackward) \ +macro(moveWordBackwardAndModifySelection) \ +macro(moveWordForward) \ +macro(moveWordForwardAndModifySelection) \ +macro(moveWordLeft) \ +macro(moveWordLeftAndModifySelection) \ +macro(moveWordRight) \ +macro(moveWordRightAndModifySelection) \ +macro(outdent) \ +macro(pageDown) \ +macro(pageUp) \ +macro(paste) \ +macro(pasteAsPlainText) \ +macro(pasteAsRichText) \ +macro(pasteFont) \ +macro(performFindPanelAction) \ +macro(scrollLineDown) \ +macro(scrollLineUp) \ +macro(scrollPageDown) \ +macro(scrollPageUp) \ +macro(scrollToBeginningOfDocument) \ +macro(scrollToEndOfDocument) \ +macro(selectAll) \ +macro(selectWord) \ +macro(selectSentence) \ +macro(selectLine) \ +macro(selectParagraph) \ +macro(showGuessPanel) \ +macro(startSpeaking) \ +macro(stopSpeaking) \ +macro(subscript) \ +macro(superscript) \ +macro(underline) \ +macro(unscript) \ +macro(uppercaseWord) \ +macro(yank) \ +macro(yankAndSelect) \ + +#define WebKitOriginalTopPrintingMarginKey @"WebKitOriginalTopMargin" +#define WebKitOriginalBottomPrintingMarginKey @"WebKitOriginalBottomMargin" + +static BOOL s_didSetCacheModel; +static WebCacheModel s_cacheModel = WebCacheModelDocumentViewer; + +static BOOL applicationIsTerminating; +static int pluginDatabaseClientCount = 0; + +@interface NSSpellChecker (AppKitSecretsIKnow) +- (void)_preflightChosenSpellServer; +@end + +@interface NSView (AppKitSecretsIKnow) +- (NSView *)_hitTest:(NSPoint *)aPoint dragTypes:(NSSet *)types; +- (void)_autoscrollForDraggingInfo:(id)dragInfo timeDelta:(NSTimeInterval)repeatDelta; +- (BOOL)_shouldAutoscrollForDraggingInfo:(id)dragInfo; +@end + +@interface NSWindow (AppKitSecretsIKnow) +- (id)_oldFirstResponderBeforeBecoming; +@end + +@interface NSObject (ValidateWithoutDelegate) +- (BOOL)validateUserInterfaceItemWithoutDelegate:(id )item; +@end + +@interface _WebSafeForwarder : NSObject +{ + id target; // Non-retained. Don't retain delegates. + id defaultTarget; + BOOL catchExceptions; +} +- (id)initWithTarget:(id)target defaultTarget:(id)defaultTarget catchExceptions:(BOOL)catchExceptions; +@end + +@interface WebViewPrivate : NSObject +{ +@public + Page* page; + + id UIDelegate; + id UIDelegateForwarder; + id resourceProgressDelegate; + id downloadDelegate; + id policyDelegate; + id policyDelegateForwarder; + id frameLoadDelegate; + id frameLoadDelegateForwarder; + id formDelegate; + id editingDelegate; + id editingDelegateForwarder; + id scriptDebugDelegate; + id scriptDebugDelegateForwarder; + + BOOL allowsUndo; + + float textSizeMultiplier; + + NSString *applicationNameForUserAgent; + String* userAgent; + BOOL userAgentOverridden; + + WebPreferences *preferences; + BOOL useSiteSpecificSpoofing; + + NSWindow *hostWindow; + + int programmaticFocusCount; + + WebResourceDelegateImplementationCache resourceLoadDelegateImplementations; + WebFrameLoadDelegateImplementationCache frameLoadDelegateImplementations; + + void *observationInfo; + + BOOL closed; + BOOL shouldCloseWithWindow; + BOOL mainFrameDocumentReady; + BOOL drawsBackground; + BOOL editable; + BOOL tabKeyCyclesThroughElementsChanged; + BOOL becomingFirstResponder; + BOOL becomingFirstResponderFromOutside; + BOOL hoverFeedbackSuspended; + BOOL usesPageCache; + BOOL catchesDelegateExceptions; + + NSColor *backgroundColor; + + NSString *mediaStyle; + + BOOL hasSpellCheckerDocumentTag; + NSInteger spellCheckerDocumentTag; + + BOOL smartInsertDeleteEnabled; + + BOOL dashboardBehaviorAlwaysSendMouseEventsToAllWindows; + BOOL dashboardBehaviorAlwaysSendActiveNullEventsToPlugIns; + BOOL dashboardBehaviorAlwaysAcceptsFirstMouse; + BOOL dashboardBehaviorAllowWheelScrolling; + + // WebKit has both a global plug-in database and a separate, per WebView plug-in database. Dashboard uses the per WebView database. + WebPluginDatabase *pluginDatabase; + + HashMap >* identifierMap; +} +@end + +@interface WebView (WebFileInternal) ++ (void)_setCacheModel:(WebCacheModel)cacheModel; ++ (WebCacheModel)_cacheModel; +- (WebFrame *)_selectedOrMainFrame; +- (WebFrameBridge *)_bridgeForSelectedOrMainFrame; +- (BOOL)_isLoading; +- (WebFrameView *)_frameViewAtWindowPoint:(NSPoint)point; +- (WebFrame *)_focusedFrame; ++ (void)_preflightSpellChecker; +- (BOOL)_continuousCheckingAllowed; +- (NSResponder *)_responderForResponderOperations; +- (BOOL)_performTextSizingSelector:(SEL)sel withObject:(id)arg onTrackingDocs:(BOOL)doTrackingViews selForNonTrackingDocs:(SEL)testSel newScaleFactor:(float)newScaleFactor; +- (void)_notifyTextSizeMultiplierChanged; +@end + +@interface WebView (WebCallDelegateFunctions) +@end + +NSString *WebElementDOMNodeKey = @"WebElementDOMNode"; +NSString *WebElementFrameKey = @"WebElementFrame"; +NSString *WebElementImageKey = @"WebElementImage"; +NSString *WebElementImageAltStringKey = @"WebElementImageAltString"; +NSString *WebElementImageRectKey = @"WebElementImageRect"; +NSString *WebElementImageURLKey = @"WebElementImageURL"; +NSString *WebElementIsSelectedKey = @"WebElementIsSelected"; +NSString *WebElementLinkLabelKey = @"WebElementLinkLabel"; +NSString *WebElementLinkTargetFrameKey = @"WebElementTargetFrame"; +NSString *WebElementLinkTitleKey = @"WebElementLinkTitle"; +NSString *WebElementLinkURLKey = @"WebElementLinkURL"; +NSString *WebElementSpellingToolTipKey = @"WebElementSpellingToolTip"; +NSString *WebElementTitleKey = @"WebElementTitle"; +NSString *WebElementLinkIsLiveKey = @"WebElementLinkIsLive"; +NSString *WebElementIsContentEditableKey = @"WebElementIsContentEditableKey"; + +NSString *WebViewProgressStartedNotification = @"WebProgressStartedNotification"; +NSString *WebViewProgressEstimateChangedNotification = @"WebProgressEstimateChangedNotification"; +NSString *WebViewProgressFinishedNotification = @"WebProgressFinishedNotification"; + +NSString * const WebViewDidBeginEditingNotification = @"WebViewDidBeginEditingNotification"; +NSString * const WebViewDidChangeNotification = @"WebViewDidChangeNotification"; +NSString * const WebViewDidEndEditingNotification = @"WebViewDidEndEditingNotification"; +NSString * const WebViewDidChangeTypingStyleNotification = @"WebViewDidChangeTypingStyleNotification"; +NSString * const WebViewDidChangeSelectionNotification = @"WebViewDidChangeSelectionNotification"; + +enum { WebViewVersion = 4 }; + +#define timedLayoutSize 4096 + +static NSMutableSet *schemesWithRepresentationsSet; + +NSString *_WebCanGoBackKey = @"canGoBack"; +NSString *_WebCanGoForwardKey = @"canGoForward"; +NSString *_WebEstimatedProgressKey = @"estimatedProgress"; +NSString *_WebIsLoadingKey = @"isLoading"; +NSString *_WebMainFrameIconKey = @"mainFrameIcon"; +NSString *_WebMainFrameTitleKey = @"mainFrameTitle"; +NSString *_WebMainFrameURLKey = @"mainFrameURL"; +NSString *_WebMainFrameDocumentKey = @"mainFrameDocument"; + +@interface WebProgressItem : NSObject +{ +@public + long long bytesReceived; + long long estimatedLength; +} +@end + +@implementation WebProgressItem +@end + +static BOOL continuousSpellCheckingEnabled; +#ifndef BUILDING_ON_TIGER +static BOOL grammarCheckingEnabled; +#endif + +@implementation WebViewPrivate + +#ifndef BUILDING_ON_TIGER ++ (void)initialize +{ + WebCoreObjCFinalizeOnMainThread(self); +} +#endif + +- init +{ + self = [super init]; + if (!self) + return nil; + allowsUndo = YES; + textSizeMultiplier = 1; + dashboardBehaviorAllowWheelScrolling = YES; + shouldCloseWithWindow = objc_collecting_enabled(); + continuousSpellCheckingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebContinuousSpellCheckingEnabled]; + +#ifndef BUILDING_ON_TIGER + grammarCheckingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebGrammarCheckingEnabled]; +#endif + userAgent = new String; + + usesPageCache = YES; + + identifierMap = new HashMap >(); + pluginDatabaseClientCount++; + + return self; +} + +- (void)dealloc +{ + ASSERT(!page); + ASSERT(!preferences); + + delete userAgent; + delete identifierMap; + + [applicationNameForUserAgent release]; + [backgroundColor release]; + + [hostWindow release]; + + [policyDelegateForwarder release]; + [UIDelegateForwarder release]; + [frameLoadDelegateForwarder release]; + [editingDelegateForwarder release]; + [scriptDebugDelegateForwarder release]; + + [mediaStyle release]; + + [super dealloc]; +} + +- (void)finalize +{ + ASSERT_MAIN_THREAD(); + + delete userAgent; + delete identifierMap; + + [super finalize]; +} + +@end + +@implementation WebView (AllWebViews) + +static CFSetCallBacks NonRetainingSetCallbacks = { + 0, + NULL, + NULL, + CFCopyDescription, + CFEqual, + CFHash +}; + +static CFMutableSetRef allWebViewsSet; + ++ (void)_makeAllWebViewsPerformSelector:(SEL)selector +{ + if (!allWebViewsSet) + return; + + [(NSMutableSet *)allWebViewsSet makeObjectsPerformSelector:selector]; +} + +- (void)_removeFromAllWebViewsSet +{ + if (allWebViewsSet) + CFSetRemoveValue(allWebViewsSet, self); +} + +- (void)_addToAllWebViewsSet +{ + if (!allWebViewsSet) + allWebViewsSet = CFSetCreateMutable(NULL, 0, &NonRetainingSetCallbacks); + + CFSetSetValue(allWebViewsSet, self); +} + +@end + +@implementation WebView (WebPrivate) + +#ifdef DEBUG_WIDGET_DRAWING +static bool debugWidget = true; +- (void)drawRect:(NSRect)rect +{ + [[NSColor blueColor] set]; + NSRectFill (rect); + + NSRect htmlViewRect = [[[[self mainFrame] frameView] documentView] frame]; + + if (debugWidget) { + while (debugWidget) { + sleep (1); + } + } + + NSLog (@"%s: rect: (%0.f,%0.f) %0.f %0.f, htmlViewRect: (%0.f,%0.f) %0.f %0.f\n", + __PRETTY_FUNCTION__, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, + htmlViewRect.origin.x, htmlViewRect.origin.y, htmlViewRect.size.width, htmlViewRect.size.height + ); + + [super drawRect:rect]; +} +#endif + ++ (BOOL)_developerExtrasEnabled +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + if ([defaults boolForKey:@"DisableWebKitDeveloperExtras"]) + return NO; +#ifdef NDEBUG + BOOL enableDebugger = [defaults boolForKey:@"WebKitDeveloperExtras"]; + if (!enableDebugger) + enableDebugger = [defaults boolForKey:@"IncludeDebugMenu"]; + return enableDebugger; +#else + return YES; // always enable in debug builds +#endif +} + ++ (BOOL)_scriptDebuggerEnabled +{ +#ifdef NDEBUG + return [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitScriptDebuggerEnabled"]; +#else + return YES; // always enable in debug builds +#endif +} + ++ (NSArray *)_supportedMIMETypes +{ + // Load the plug-in DB allowing plug-ins to install types. + [WebPluginDatabase sharedDatabase]; + return [[WebFrameView _viewTypesAllowImageTypeOmission:NO] allKeys]; +} + ++ (NSArray *)_supportedFileExtensions +{ + NSMutableSet *extensions = [[NSMutableSet alloc] init]; + NSArray *MIMETypes = [self _supportedMIMETypes]; + NSEnumerator *enumerator = [MIMETypes objectEnumerator]; + NSString *MIMEType; + while ((MIMEType = [enumerator nextObject]) != nil) { + NSArray *extensionsForType = WKGetExtensionsForMIMEType(MIMEType); + if (extensionsForType) { + [extensions addObjectsFromArray:extensionsForType]; + } + } + NSArray *uniqueExtensions = [extensions allObjects]; + [extensions release]; + return uniqueExtensions; +} + ++ (BOOL)_viewClass:(Class *)vClass andRepresentationClass:(Class *)rClass forMIMEType:(NSString *)MIMEType; +{ + MIMEType = [MIMEType lowercaseString]; + Class viewClass = [[WebFrameView _viewTypesAllowImageTypeOmission:YES] _webkit_objectForMIMEType:MIMEType]; + Class repClass = [[WebDataSource _repTypesAllowImageTypeOmission:YES] _webkit_objectForMIMEType:MIMEType]; + + if (!viewClass || !repClass || [[WebPDFView supportedMIMETypes] containsObject:MIMEType]) { + // Our optimization to avoid loading the plug-in DB and image types for the HTML case failed. + // Load the plug-in DB allowing plug-ins to install types. + [WebPluginDatabase sharedDatabase]; + + // Load the image types and get the view class and rep class. This should be the fullest picture of all handled types. + viewClass = [[WebFrameView _viewTypesAllowImageTypeOmission:NO] _webkit_objectForMIMEType:MIMEType]; + repClass = [[WebDataSource _repTypesAllowImageTypeOmission:NO] _webkit_objectForMIMEType:MIMEType]; + } + + if (viewClass && repClass) { + // Special-case WebHTMLView for text types that shouldn't be shown. + if (viewClass == [WebHTMLView class] && + repClass == [WebHTMLRepresentation class] && + [[WebHTMLView unsupportedTextMIMETypes] containsObject:MIMEType]) { + return NO; + } + if (vClass) + *vClass = viewClass; + if (rClass) + *rClass = repClass; + return YES; + } + + return NO; +} + +- (BOOL)_viewClass:(Class *)vClass andRepresentationClass:(Class *)rClass forMIMEType:(NSString *)MIMEType; +{ + if ([[self class] _viewClass:vClass andRepresentationClass:rClass forMIMEType:MIMEType]) + return YES; + + if (_private->pluginDatabase) { + WebBasePluginPackage *pluginPackage = [_private->pluginDatabase pluginForMIMEType:MIMEType]; + if (pluginPackage) { + if (vClass) + *vClass = [WebHTMLView class]; + if (rClass) + *rClass = [WebHTMLRepresentation class]; + return YES; + } + } + + return NO; +} + ++ (void)_setAlwaysUseATSU:(BOOL)f +{ + WebCoreSetAlwaysUseATSU(f); +} + ++ (BOOL)canShowFile:(NSString *)path +{ + return [[self class] canShowMIMEType:[WebView _MIMETypeForFile:path]]; +} + ++ (NSString *)suggestedFileExtensionForMIMEType:(NSString *)type +{ + return WKGetPreferredExtensionForMIMEType(type); +} + +- (BOOL)_isClosed +{ + if (!_private || _private->closed) + return YES; + return NO; +} + +- (void)_close +{ + if (!_private || _private->closed) + return; + + FrameLoader* mainFrameLoader = [[self mainFrame] _frameLoader]; + if (mainFrameLoader) + mainFrameLoader->detachFromParent(); + + [self _removeFromAllWebViewsSet]; + [self setGroupName:nil]; + [self setHostWindow:nil]; + + [self setDownloadDelegate:nil]; + [self setEditingDelegate:nil]; + [self setFrameLoadDelegate:nil]; + [self setPolicyDelegate:nil]; + [self setResourceLoadDelegate:nil]; + [self setScriptDebugDelegate:nil]; + [self setUIDelegate:nil]; + + // setHostWindow:nil must be called before this value is set (see 5408186) + _private->closed = YES; + + // To avoid leaks, call removeDragCaret in case it wasn't called after moveDragCaretToPoint. + [self removeDragCaret]; + + // Deleteing the WebCore::Page will clear the page cache so we call destroy on + // all the plug-ins in the page cache to break any retain cycles. + // See comment in HistoryItem::releaseAllPendingPageCaches() for more information. + delete _private->page; + _private->page = 0; + + if (_private->hasSpellCheckerDocumentTag) { + [[NSSpellChecker sharedSpellChecker] closeSpellDocumentWithTag:_private->spellCheckerDocumentTag]; + _private->hasSpellCheckerDocumentTag = NO; + } + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [WebPreferences _removeReferenceForIdentifier:[self preferencesIdentifier]]; + + WebPreferences *preferences = _private->preferences; + _private->preferences = nil; + [preferences didRemoveFromWebView]; + [preferences release]; + + pluginDatabaseClientCount--; + + // Make sure to close both sets of plug-ins databases because plug-ins need an opportunity to clean up files, etc. + + // Unload the WebView local plug-in database. + if (_private->pluginDatabase) { + [_private->pluginDatabase close]; + [_private->pluginDatabase release]; + _private->pluginDatabase = nil; + } + + // Keep the global plug-in database active until the app terminates to avoid having to reload plug-in bundles. + if (!pluginDatabaseClientCount && applicationIsTerminating) + [WebPluginDatabase closeSharedDatabase]; +} + ++ (NSString *)_MIMETypeForFile:(NSString *)path +{ + NSString *extension = [path pathExtension]; + NSString *MIMEType = nil; + + // Get the MIME type from the extension. + if ([extension length] != 0) { + MIMEType = WKGetMIMETypeForExtension(extension); + } + + // If we can't get a known MIME type from the extension, sniff. + if ([MIMEType length] == 0 || [MIMEType isEqualToString:@"application/octet-stream"]) { + NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:path]; + NSData *data = [handle readDataOfLength:WEB_GUESS_MIME_TYPE_PEEK_LENGTH]; + [handle closeFile]; + if ([data length] != 0) { + MIMEType = [data _webkit_guessedMIMEType]; + } + if ([MIMEType length] == 0) { + MIMEType = @"application/octet-stream"; + } + } + + return MIMEType; +} + +- (WebDownload *)_downloadURL:(NSURL *)URL +{ + ASSERT(URL); + + NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; + WebDownload *download = [WebDownload _downloadWithRequest:request + delegate:_private->downloadDelegate + directory:nil]; + [request release]; + + return download; +} + +- (WebView *)_openNewWindowWithRequest:(NSURLRequest *)request +{ + WebView *newWindowWebView = CallUIDelegate(self, @selector(webView:createWebViewWithRequest:), request); + if (!newWindowWebView) + return nil; + + CallUIDelegate(newWindowWebView, @selector(webViewShow:)); + return newWindowWebView; +} + +- (WebCore::Page*)page +{ + return _private->page; +} + +- (NSMenu *)_menuForElement:(NSDictionary *)element defaultItems:(NSArray *)items +{ + NSArray *defaultMenuItems = [[WebDefaultUIDelegate sharedUIDelegate] webView:self contextMenuItemsForElement:element defaultMenuItems:items]; + + NSArray *menuItems = CallUIDelegate(self, @selector(webView:contextMenuItemsForElement:defaultMenuItems:), element, defaultMenuItems); + if (!menuItems) + return nil; + + unsigned count = [menuItems count]; + if (!count) + return nil; + + NSMenu *menu = [[NSMenu alloc] init]; + for (unsigned i = 0; i < count; i++) + [menu addItem:[menuItems objectAtIndex:i]]; + + return [menu autorelease]; +} + +- (void)_mouseDidMoveOverElement:(NSDictionary *)dictionary modifierFlags:(NSUInteger)modifierFlags +{ + // We originally intended to call this delegate method sometimes with a nil dictionary, but due to + // a bug dating back to WebKit 1.0 this delegate was never called with nil! Unfortunately we can't + // start calling this with nil since it will break Adobe Help Viewer, and possibly other clients. + if (!dictionary) + return; + CallUIDelegate(self, @selector(webView:mouseDidMoveOverElement:modifierFlags:), dictionary, modifierFlags); +} + +- (void)_loadBackForwardListFromOtherView:(WebView *)otherView +{ + if (!_private->page) + return; + + if (!otherView->_private->page) + return; + + // It turns out the right combination of behavior is done with the back/forward load + // type. (See behavior matrix at the top of WebFramePrivate.) So we copy all the items + // in the back forward list, and go to the current one. + + BackForwardList* backForwardList = _private->page->backForwardList(); + ASSERT(!backForwardList->currentItem()); // destination list should be empty + + BackForwardList* otherBackForwardList = otherView->_private->page->backForwardList(); + if (!otherBackForwardList->currentItem()) + return; // empty back forward list, bail + + HistoryItem* newItemToGoTo = 0; + + int lastItemIndex = otherBackForwardList->forwardListCount(); + for (int i = -otherBackForwardList->backListCount(); i <= lastItemIndex; ++i) { + if (i == 0) { + // If this item is showing , save away its current scroll and form state, + // since that might have changed since loading and it is normally not saved + // until we leave that page. + otherView->_private->page->mainFrame()->loader()->saveDocumentAndScrollState(); + } + RefPtr newItem = otherBackForwardList->itemAtIndex(i)->copy(); + if (i == 0) + newItemToGoTo = newItem.get(); + backForwardList->addItem(newItem.release()); + } + + ASSERT(newItemToGoTo); + _private->page->goToItem(newItemToGoTo, FrameLoadTypeIndexedBackForward); +} + +- (void)_setFormDelegate: (id)delegate +{ + _private->formDelegate = delegate; +} + +- (id)_formDelegate +{ + return _private->formDelegate; +} + +- (BOOL)_needsAdobeFrameReloadingQuirk +{ + static BOOL checked = NO; + static BOOL needsQuirk = NO; + + if (checked) + return needsQuirk; + + needsQuirk = WKAppVersionCheckLessThan(@"com.adobe.Acrobat", -1, 9.0) + || WKAppVersionCheckLessThan(@"com.adobe.Acrobat.Pro", -1, 9.0) + || WKAppVersionCheckLessThan(@"com.adobe.Reader", -1, 9.0) + || WKAppVersionCheckLessThan(@"com.adobe.distiller", -1, 9.0) + || WKAppVersionCheckLessThan(@"com.adobe.Contribute", -1, 4.2) + || WKAppVersionCheckLessThan(@"com.adobe.dreamweaver-9.0", -1, 9.1) + || WKAppVersionCheckLessThan(@"com.macromedia.fireworks", -1, 9.1) + || WKAppVersionCheckLessThan(@"com.adobe.InCopy", -1, 5.1) + || WKAppVersionCheckLessThan(@"com.adobe.InDesign", -1, 5.1) + || WKAppVersionCheckLessThan(@"com.adobe.Soundbooth", -1, 2); + + return needsQuirk; +} + +- (void)_preferencesChangedNotification:(NSNotification *)notification +{ + WebPreferences *preferences = (WebPreferences *)[notification object]; + ASSERT(preferences == [self preferences]); + + if (!_private->userAgentOverridden) + *_private->userAgent = String(); + + // Cache this value so we don't have to read NSUserDefaults on each page load + _private->useSiteSpecificSpoofing = [preferences _useSiteSpecificSpoofing]; + + // Update corresponding WebCore Settings object. + if (!_private->page) + return; + + Settings* settings = _private->page->settings(); + + settings->setCursiveFontFamily([preferences cursiveFontFamily]); + settings->setDefaultFixedFontSize([preferences defaultFixedFontSize]); + settings->setDefaultFontSize([preferences defaultFontSize]); + settings->setDefaultTextEncodingName([preferences defaultTextEncodingName]); + settings->setFantasyFontFamily([preferences fantasyFontFamily]); + settings->setFixedFontFamily([preferences fixedFontFamily]); + settings->setForceFTPDirectoryListings([preferences _forceFTPDirectoryListings]); + settings->setFTPDirectoryTemplatePath([preferences _ftpDirectoryTemplatePath]); + settings->setJavaEnabled([preferences isJavaEnabled]); + settings->setJavaScriptEnabled([preferences isJavaScriptEnabled]); + settings->setJavaScriptCanOpenWindowsAutomatically([preferences javaScriptCanOpenWindowsAutomatically]); + settings->setMinimumFontSize([preferences minimumFontSize]); + settings->setMinimumLogicalFontSize([preferences minimumLogicalFontSize]); + settings->setPluginsEnabled([preferences arePlugInsEnabled]); + settings->setPrivateBrowsingEnabled([preferences privateBrowsingEnabled]); + settings->setSansSerifFontFamily([preferences sansSerifFontFamily]); + settings->setSerifFontFamily([preferences serifFontFamily]); + settings->setStandardFontFamily([preferences standardFontFamily]); + settings->setLoadsImagesAutomatically([preferences loadsImagesAutomatically]); + settings->setShouldPrintBackgrounds([preferences shouldPrintBackgrounds]); + settings->setTextAreasAreResizable([preferences textAreasAreResizable]); + settings->setShrinksStandaloneImagesToFit([preferences shrinksStandaloneImagesToFit]); + settings->setEditableLinkBehavior(core([preferences editableLinkBehavior])); + settings->setDOMPasteAllowed([preferences isDOMPasteAllowed]); + settings->setUsesPageCache([self usesPageCache]); + settings->setShowsURLsInToolTips([preferences showsURLsInToolTips]); + settings->setDeveloperExtrasEnabled([WebView _developerExtrasEnabled]); + if ([preferences userStyleSheetEnabled]) { + NSString* location = [[preferences userStyleSheetLocation] _web_originalDataAsString]; + settings->setUserStyleSheetLocation([NSURL URLWithString:(location ? location : @"")]); + } else + settings->setUserStyleSheetLocation([NSURL URLWithString:@""]); + settings->setNeedsAdobeFrameReloadingQuirk([self _needsAdobeFrameReloadingQuirk]); +} + +static inline IMP getMethod(id o, SEL s) +{ + return [o respondsToSelector:s] ? [o methodForSelector:s] : 0; +} + +- (void)_cacheResourceLoadDelegateImplementations +{ + WebResourceDelegateImplementationCache *cache = &_private->resourceLoadDelegateImplementations; + id delegate = _private->resourceProgressDelegate; + + cache->didCancelAuthenticationChallengeFunc = getMethod(delegate, @selector(webView:resource:didReceiveAuthenticationChallenge:fromDataSource:)); + cache->didFailLoadingWithErrorFromDataSourceFunc = getMethod(delegate, @selector(webView:resource:didFailLoadingWithError:fromDataSource:)); + cache->didFinishLoadingFromDataSourceFunc = getMethod(delegate, @selector(webView:resource:didFinishLoadingFromDataSource:)); + cache->didLoadResourceFromMemoryCacheFunc = getMethod(delegate, @selector(webView:didLoadResourceFromMemoryCache:response:length:fromDataSource:)); + cache->didReceiveAuthenticationChallengeFunc = getMethod(delegate, @selector(webView:resource:didReceiveAuthenticationChallenge:fromDataSource:)); + cache->didReceiveContentLengthFunc = getMethod(delegate, @selector(webView:resource:didReceiveContentLength:fromDataSource:)); + cache->didReceiveResponseFunc = getMethod(delegate, @selector(webView:resource:didReceiveResponse:fromDataSource:)); + cache->identifierForRequestFunc = getMethod(delegate, @selector(webView:identifierForInitialRequest:fromDataSource:)); + cache->plugInFailedWithErrorFunc = getMethod(delegate, @selector(webView:plugInFailedWithError:dataSource:)); + cache->willCacheResponseFunc = getMethod(delegate, @selector(webView:resource:willCacheResponse:fromDataSource:)); + cache->willSendRequestFunc = getMethod(delegate, @selector(webView:resource:willSendRequest:redirectResponse:fromDataSource:)); +} + +WebResourceDelegateImplementationCache WebViewGetResourceLoadDelegateImplementations(WebView *webView) +{ + return webView->_private->resourceLoadDelegateImplementations; +} + +- (void)_cacheFrameLoadDelegateImplementations +{ + WebFrameLoadDelegateImplementationCache *cache = &_private->frameLoadDelegateImplementations; + id delegate = _private->frameLoadDelegate; + + cache->didCancelClientRedirectForFrameFunc = getMethod(delegate, @selector(webView:didCancelClientRedirectForFrame:)); + cache->didChangeLocationWithinPageForFrameFunc = getMethod(delegate, @selector(webView:didChangeLocationWithinPageForFrame:)); + cache->didClearWindowObjectForFrameFunc = getMethod(delegate, @selector(webView:didClearWindowObject:forFrame:)); + cache->didCommitLoadForFrameFunc = getMethod(delegate, @selector(webView:didCommitLoadForFrame:)); + cache->didFailLoadWithErrorForFrameFunc = getMethod(delegate, @selector(webView:didFailLoadWithError:forFrame:)); + cache->didFailProvisionalLoadWithErrorForFrameFunc = getMethod(delegate, @selector(webView:didFailProvisionalLoadWithError:forFrame:)); + cache->didFinishDocumentLoadForFrameFunc = getMethod(delegate, @selector(webView:didFinishDocumentLoadForFrame:)); + cache->didFinishLoadForFrameFunc = getMethod(delegate, @selector(webView:didFinishLoadForFrame:)); + cache->didFirstLayoutInFrameFunc = getMethod(delegate, @selector(webView:didFirstLayoutInFrame:)); + cache->didHandleOnloadEventsForFrameFunc = getMethod(delegate, @selector(webView:didHandleOnloadEventsForFrame:)); + cache->didReceiveIconForFrameFunc = getMethod(delegate, @selector(webView:didReceiveIcon:forFrame:)); + cache->didReceiveServerRedirectForProvisionalLoadForFrameFunc = getMethod(delegate, @selector(webView:didReceiveServerRedirectForProvisionalLoadForFrame:)); + cache->didReceiveTitleForFrameFunc = getMethod(delegate, @selector(webView:didReceiveTitle:forFrame:)); + cache->didStartProvisionalLoadForFrameFunc = getMethod(delegate, @selector(webView:didStartProvisionalLoadForFrame:)); + cache->willCloseFrameFunc = getMethod(delegate, @selector(webView:willCloseFrame:)); + cache->willPerformClientRedirectToURLDelayFireDateForFrameFunc = getMethod(delegate, @selector(webView:willPerformClientRedirectToURL:delay:fireDate:forFrame:)); + cache->windowScriptObjectAvailableFunc = getMethod(delegate, @selector(webView:windowScriptObjectAvailable:)); +} + +WebFrameLoadDelegateImplementationCache WebViewGetFrameLoadDelegateImplementations(WebView *webView) +{ + return webView->_private->frameLoadDelegateImplementations; +} + +- (id)_policyDelegateForwarder +{ + if (!_private->policyDelegateForwarder) + _private->policyDelegateForwarder = [[_WebSafeForwarder alloc] initWithTarget:_private->policyDelegate defaultTarget:[WebDefaultPolicyDelegate sharedPolicyDelegate] catchExceptions:_private->catchesDelegateExceptions]; + return _private->policyDelegateForwarder; +} + +- (id)_UIDelegateForwarder +{ + if (!_private->UIDelegateForwarder) + _private->UIDelegateForwarder = [[_WebSafeForwarder alloc] initWithTarget:_private->UIDelegate defaultTarget:[WebDefaultUIDelegate sharedUIDelegate] catchExceptions:_private->catchesDelegateExceptions]; + return _private->UIDelegateForwarder; +} + +- (id)_editingDelegateForwarder +{ + // This can be called during window deallocation by QTMovieView in the QuickTime Cocoa Plug-in. + // Not sure if that is a bug or not. + if (!_private) + return nil; + + if (!_private->editingDelegateForwarder) + _private->editingDelegateForwarder = [[_WebSafeForwarder alloc] initWithTarget:_private->editingDelegate defaultTarget:[WebDefaultEditingDelegate sharedEditingDelegate] catchExceptions:_private->catchesDelegateExceptions]; + return _private->editingDelegateForwarder; +} + +- (id)_scriptDebugDelegateForwarder +{ + if (!_private->scriptDebugDelegateForwarder) + _private->scriptDebugDelegateForwarder = [[_WebSafeForwarder alloc] initWithTarget:_private->scriptDebugDelegate defaultTarget:[WebDefaultScriptDebugDelegate sharedScriptDebugDelegate] catchExceptions:_private->catchesDelegateExceptions]; + return _private->scriptDebugDelegateForwarder; +} + +- (void)_closeWindow +{ + [[self _UIDelegateForwarder] webViewClose:self]; +} + ++ (void)_unregisterViewClassAndRepresentationClassForMIMEType:(NSString *)MIMEType; +{ + [[WebFrameView _viewTypesAllowImageTypeOmission:NO] removeObjectForKey:MIMEType]; + [[WebDataSource _repTypesAllowImageTypeOmission:NO] removeObjectForKey:MIMEType]; + + // FIXME: We also need to maintain MIMEType registrations (which can be dynamically changed) + // in the WebCore MIMEType registry. For now we're doing this in a safe, limited manner + // to fix - a future revamping of the entire system is neccesary for future robustness + MIMETypeRegistry::getSupportedNonImageMIMETypes().remove(MIMEType); +} + ++ (void)_registerViewClass:(Class)viewClass representationClass:(Class)representationClass forURLScheme:(NSString *)URLScheme; +{ + NSString *MIMEType = [self _generatedMIMETypeForURLScheme:URLScheme]; + [self registerViewClass:viewClass representationClass:representationClass forMIMEType:MIMEType]; + + // FIXME: We also need to maintain MIMEType registrations (which can be dynamically changed) + // in the WebCore MIMEType registry. For now we're doing this in a safe, limited manner + // to fix - a future revamping of the entire system is neccesary for future robustness + if ([viewClass class] == [WebHTMLView class]) + MIMETypeRegistry::getSupportedNonImageMIMETypes().add(MIMEType); + + // This is used to make _representationExistsForURLScheme faster. + // Without this set, we'd have to create the MIME type each time. + if (schemesWithRepresentationsSet == nil) { + schemesWithRepresentationsSet = [[NSMutableSet alloc] init]; + } + [schemesWithRepresentationsSet addObject:[[[URLScheme lowercaseString] copy] autorelease]]; +} + ++ (NSString *)_generatedMIMETypeForURLScheme:(NSString *)URLScheme +{ + return [@"x-apple-web-kit/" stringByAppendingString:[URLScheme lowercaseString]]; +} + ++ (BOOL)_representationExistsForURLScheme:(NSString *)URLScheme +{ + return [schemesWithRepresentationsSet containsObject:[URLScheme lowercaseString]]; +} + ++ (BOOL)_canHandleRequest:(NSURLRequest *)request +{ + // FIXME: If gets fixed, this check can be removed + if (!request) + return NO; + + if ([NSURLConnection canHandleRequest:request]) + return YES; + + NSString *scheme = [[request URL] scheme]; + + if ([self _representationExistsForURLScheme:scheme]) + return YES; + + return ([scheme _webkit_isCaseInsensitiveEqualToString:@"applewebdata"]); +} + ++ (NSString *)_decodeData:(NSData *)data +{ + HTMLNames::init(); // this method is used for importing bookmarks at startup, so HTMLNames are likely to be uninitialized yet + RefPtr decoder = new TextResourceDecoder("text/html"); // bookmark files are HTML + String result = decoder->decode(static_cast([data bytes]), [data length]); + result += decoder->flush(); + return result; +} + +- (void)_pushPerformingProgrammaticFocus +{ + _private->programmaticFocusCount++; +} + +- (void)_popPerformingProgrammaticFocus +{ + _private->programmaticFocusCount--; +} + +- (BOOL)_isPerformingProgrammaticFocus +{ + return _private->programmaticFocusCount != 0; +} + +- (void)_didChangeValueForKey: (NSString *)key +{ + LOG (Bindings, "calling didChangeValueForKey: %@", key); + [self didChangeValueForKey: key]; +} + +- (void)_willChangeValueForKey: (NSString *)key +{ + LOG (Bindings, "calling willChangeValueForKey: %@", key); + [self willChangeValueForKey: key]; +} + ++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { + static NSSet *manualNotifyKeys = nil; + if (!manualNotifyKeys) + manualNotifyKeys = [[NSSet alloc] initWithObjects:_WebMainFrameURLKey, _WebIsLoadingKey, _WebEstimatedProgressKey, + _WebCanGoBackKey, _WebCanGoForwardKey, _WebMainFrameTitleKey, _WebMainFrameIconKey, _WebMainFrameDocumentKey, nil]; + if ([manualNotifyKeys containsObject:key]) + return NO; + return YES; +} + +- (NSArray *)_declaredKeys { + static NSArray *declaredKeys = nil; + if (!declaredKeys) + declaredKeys = [[NSArray alloc] initWithObjects:_WebMainFrameURLKey, _WebIsLoadingKey, _WebEstimatedProgressKey, + _WebCanGoBackKey, _WebCanGoForwardKey, _WebMainFrameTitleKey, _WebMainFrameIconKey, _WebMainFrameDocumentKey, nil]; + return declaredKeys; +} + +- (void)setObservationInfo:(void *)info +{ + _private->observationInfo = info; +} + +- (void *)observationInfo +{ + return _private->observationInfo; +} + +- (void)_willChangeBackForwardKeys +{ + [self _willChangeValueForKey: _WebCanGoBackKey]; + [self _willChangeValueForKey: _WebCanGoForwardKey]; +} + +- (void)_didChangeBackForwardKeys +{ + [self _didChangeValueForKey: _WebCanGoBackKey]; + [self _didChangeValueForKey: _WebCanGoForwardKey]; +} + +- (void)_didStartProvisionalLoadForFrame:(WebFrame *)frame +{ + [self _willChangeBackForwardKeys]; + if (frame == [self mainFrame]){ + // Force an observer update by sending a will/did. + [self _willChangeValueForKey: _WebIsLoadingKey]; + [self _didChangeValueForKey: _WebIsLoadingKey]; + + [self _willChangeValueForKey: _WebMainFrameURLKey]; + } + + [NSApp setWindowsNeedUpdate:YES]; +} + +- (void)_didCommitLoadForFrame:(WebFrame *)frame +{ + if (frame == [self mainFrame]) + [self _didChangeValueForKey: _WebMainFrameURLKey]; + [NSApp setWindowsNeedUpdate:YES]; +} + +- (void)_didFinishLoadForFrame:(WebFrame *)frame +{ + [self _didChangeBackForwardKeys]; + if (frame == [self mainFrame]){ + // Force an observer update by sending a will/did. + [self _willChangeValueForKey: _WebIsLoadingKey]; + [self _didChangeValueForKey: _WebIsLoadingKey]; + } + [NSApp setWindowsNeedUpdate:YES]; +} + +- (void)_didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame +{ + [self _didChangeBackForwardKeys]; + if (frame == [self mainFrame]){ + // Force an observer update by sending a will/did. + [self _willChangeValueForKey: _WebIsLoadingKey]; + [self _didChangeValueForKey: _WebIsLoadingKey]; + } + [NSApp setWindowsNeedUpdate:YES]; +} + +- (void)_didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame +{ + [self _didChangeBackForwardKeys]; + if (frame == [self mainFrame]){ + // Force an observer update by sending a will/did. + [self _willChangeValueForKey: _WebIsLoadingKey]; + [self _didChangeValueForKey: _WebIsLoadingKey]; + + [self _didChangeValueForKey: _WebMainFrameURLKey]; + } + [NSApp setWindowsNeedUpdate:YES]; +} + +- (void)_reloadForPluginChanges +{ + [[self mainFrame] _reloadForPluginChanges]; +} + +- (NSCachedURLResponse *)_cachedResponseForURL:(NSURL *)URL +{ + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL]; + [request _web_setHTTPUserAgent:[self userAgentForURL:URL]]; + NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; + [request release]; + return cachedResponse; +} + +- (void)_writeImageForElement:(NSDictionary *)element withPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard +{ + NSURL *linkURL = [element objectForKey:WebElementLinkURLKey]; + DOMElement *domElement = [element objectForKey:WebElementDOMNodeKey]; + [pasteboard _web_writeImage:(NSImage *)(domElement ? nil : [element objectForKey:WebElementImageKey]) + element:domElement + URL:linkURL ? linkURL : (NSURL *)[element objectForKey:WebElementImageURLKey] + title:[element objectForKey:WebElementImageAltStringKey] + archive:[[element objectForKey:WebElementDOMNodeKey] webArchive] + types:types + source:nil]; +} + +- (void)_writeLinkElement:(NSDictionary *)element withPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard +{ + [pasteboard _web_writeURL:[element objectForKey:WebElementLinkURLKey] + andTitle:[element objectForKey:WebElementLinkLabelKey] + types:types]; +} + +- (void)_setInitiatedDrag:(BOOL)initiatedDrag +{ + if (!_private->page) + return; + _private->page->dragController()->setDidInitiateDrag(initiatedDrag); +} + +#define DASHBOARD_CONTROL_LABEL @"control" + +- (void)_addScrollerDashboardRegions:(NSMutableDictionary *)regions from:(NSArray *)views +{ + // Add scroller regions for NSScroller and KWQScrollBar + int i, count = [views count]; + + for (i = 0; i < count; i++) { + NSView *aView = [views objectAtIndex:i]; + + if ([aView isKindOfClass:[NSScroller class]] || + [aView isKindOfClass:NSClassFromString (@"KWQScrollBar")]) { + NSRect bounds = [aView bounds]; + NSRect adjustedBounds; + adjustedBounds.origin = [self convertPoint:bounds.origin fromView:aView]; + adjustedBounds.origin.y = [self bounds].size.height - adjustedBounds.origin.y; + + // AppKit has horrible hack of placing absent scrollers at -100,-100 + if (adjustedBounds.origin.y == -100) + continue; + adjustedBounds.size = bounds.size; + NSRect clip = [aView visibleRect]; + NSRect adjustedClip; + adjustedClip.origin = [self convertPoint:clip.origin fromView:aView]; + adjustedClip.origin.y = [self bounds].size.height - adjustedClip.origin.y; + adjustedClip.size = clip.size; + WebDashboardRegion *aRegion = + [[[WebDashboardRegion alloc] initWithRect:adjustedBounds + clip:adjustedClip type:WebDashboardRegionTypeScrollerRectangle] autorelease]; + NSMutableArray *scrollerRegions; + scrollerRegions = [regions objectForKey:DASHBOARD_CONTROL_LABEL]; + if (!scrollerRegions) { + scrollerRegions = [NSMutableArray array]; + [regions setObject:scrollerRegions forKey:DASHBOARD_CONTROL_LABEL]; + } + [scrollerRegions addObject:aRegion]; + } + [self _addScrollerDashboardRegions:regions from:[aView subviews]]; + } +} + +- (void)_addScrollerDashboardRegions:(NSMutableDictionary *)regions +{ + [self _addScrollerDashboardRegions:regions from:[self subviews]]; +} + +- (NSDictionary *)_dashboardRegions +{ + // Only return regions from main frame. + Frame* mainFrame = [[[self mainFrame] _bridge] _frame]; + if (!mainFrame) + return nil; + NSMutableDictionary *regions = mainFrame->dashboardRegionsDictionary(); + [self _addScrollerDashboardRegions:regions]; + return regions; +} + +- (void)_setDashboardBehavior:(WebDashboardBehavior)behavior to:(BOOL)flag +{ + // FIXME: Remove this blanket assignment once Dashboard and Dashcode implement + // specific support for the backward compatibility mode flag. + if (behavior == WebDashboardBehaviorAllowWheelScrolling && flag == NO && _private->page) + _private->page->settings()->setUsesDashboardBackwardCompatibilityMode(true); + + switch (behavior) { + case WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows: { + _private->dashboardBehaviorAlwaysSendMouseEventsToAllWindows = flag; + break; + } + case WebDashboardBehaviorAlwaysSendActiveNullEventsToPlugIns: { + _private->dashboardBehaviorAlwaysSendActiveNullEventsToPlugIns = flag; + break; + } + case WebDashboardBehaviorAlwaysAcceptsFirstMouse: { + _private->dashboardBehaviorAlwaysAcceptsFirstMouse = flag; + break; + } + case WebDashboardBehaviorAllowWheelScrolling: { + _private->dashboardBehaviorAllowWheelScrolling = flag; + break; + } + case WebDashboardBehaviorUseBackwardCompatibilityMode: { + if (_private->page) + _private->page->settings()->setUsesDashboardBackwardCompatibilityMode(flag); + break; + } + } +} + +- (BOOL)_dashboardBehavior:(WebDashboardBehavior)behavior +{ + switch (behavior) { + case WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows: { + return _private->dashboardBehaviorAlwaysSendMouseEventsToAllWindows; + } + case WebDashboardBehaviorAlwaysSendActiveNullEventsToPlugIns: { + return _private->dashboardBehaviorAlwaysSendActiveNullEventsToPlugIns; + } + case WebDashboardBehaviorAlwaysAcceptsFirstMouse: { + return _private->dashboardBehaviorAlwaysAcceptsFirstMouse; + } + case WebDashboardBehaviorAllowWheelScrolling: { + return _private->dashboardBehaviorAllowWheelScrolling; + } + case WebDashboardBehaviorUseBackwardCompatibilityMode: { + return _private->page && _private->page->settings()->usesDashboardBackwardCompatibilityMode(); + } + } + return NO; +} + ++ (void)_setShouldUseFontSmoothing:(BOOL)f +{ + WebCoreSetShouldUseFontSmoothing(f); +} + ++ (BOOL)_shouldUseFontSmoothing +{ + return WebCoreShouldUseFontSmoothing(); +} + ++ (void)_setUsesTestModeFocusRingColor:(BOOL)f +{ + setUsesTestModeFocusRingColor(f); +} + ++ (BOOL)_usesTestModeFocusRingColor +{ + return usesTestModeFocusRingColor(); +} + +// This is only used by older versions of Safari and should be removed in a future release. ++ (NSString *)_minimumRequiredSafariBuildNumber +{ + return @"420+"; +} + +- (void)setAlwaysShowVerticalScroller:(BOOL)flag +{ + WebDynamicScrollBarsView *scrollview = [[[self mainFrame] frameView] _scrollView]; + if (flag) { + [scrollview setVerticalScrollingMode:WebCoreScrollbarAlwaysOn andLock:YES]; + } else { + [scrollview setVerticalScrollingModeLocked:NO]; + [scrollview setVerticalScrollingMode:WebCoreScrollbarAuto]; + } +} + +- (BOOL)alwaysShowVerticalScroller +{ + WebDynamicScrollBarsView *scrollview = [[[self mainFrame] frameView] _scrollView]; + return [scrollview verticalScrollingModeLocked] && [scrollview verticalScrollingMode] == WebCoreScrollbarAlwaysOn; +} + +- (void)setAlwaysShowHorizontalScroller:(BOOL)flag +{ + WebDynamicScrollBarsView *scrollview = [[[self mainFrame] frameView] _scrollView]; + if (flag) { + [scrollview setHorizontalScrollingMode:WebCoreScrollbarAlwaysOn andLock:YES]; + } else { + [scrollview setHorizontalScrollingModeLocked:NO]; + [scrollview setHorizontalScrollingMode:WebCoreScrollbarAuto]; + } +} + +- (void)setProhibitsMainFrameScrolling:(BOOL)prohibits +{ + Frame* mainFrame = [[[self mainFrame] _bridge] _frame]; + if (mainFrame) + mainFrame->setProhibitsScrolling(prohibits); +} + +- (BOOL)alwaysShowHorizontalScroller +{ + WebDynamicScrollBarsView *scrollview = [[[self mainFrame] frameView] _scrollView]; + return [scrollview horizontalScrollingModeLocked] && [scrollview horizontalScrollingMode] == WebCoreScrollbarAlwaysOn; +} + +- (void)_setInViewSourceMode:(BOOL)flag +{ + Frame* mainFrame = [[[self mainFrame] _bridge] _frame]; + if (mainFrame) + mainFrame->setInViewSourceMode(flag); +} + +- (BOOL)_inViewSourceMode +{ + Frame* mainFrame = [[[self mainFrame] _bridge] _frame]; + return mainFrame && mainFrame->inViewSourceMode(); +} + +- (void)_setUseFastImageScalingMode:(BOOL)flag +{ + if (_private->page && _private->page->inLowQualityImageInterpolationMode() != flag) { + _private->page->setInLowQualityImageInterpolationMode(flag); + [self setNeedsDisplay:YES]; + } +} + +- (BOOL)_inFastImageScalingMode +{ + if (_private->page) + return _private->page->inLowQualityImageInterpolationMode(); + return NO; +} + +- (void)_setAdditionalWebPlugInPaths:(NSArray *)newPaths +{ + if (!_private->pluginDatabase) + _private->pluginDatabase = [[WebPluginDatabase alloc] init]; + + [_private->pluginDatabase setPlugInPaths:newPaths]; + [_private->pluginDatabase refresh]; +} + +- (void)_attachScriptDebuggerToAllFrames +{ + for (Frame* frame = core([self mainFrame]); frame; frame = frame->tree()->traverseNext()) + [kit(frame) _attachScriptDebugger]; +} + +- (void)_detachScriptDebuggerFromAllFrames +{ + for (Frame* frame = core([self mainFrame]); frame; frame = frame->tree()->traverseNext()) + [kit(frame) _detachScriptDebugger]; +} + +- (void)setBackgroundColor:(NSColor *)backgroundColor +{ + if ([_private->backgroundColor isEqual:backgroundColor]) + return; + + id old = _private->backgroundColor; + _private->backgroundColor = [backgroundColor retain]; + [old release]; + + [[self mainFrame] _updateBackground]; +} + +- (NSColor *)backgroundColor +{ + return _private->backgroundColor; +} + +- (BOOL)defersCallbacks +{ + if (!_private->page) + return NO; + return _private->page->defersLoading(); +} + +- (void)setDefersCallbacks:(BOOL)defer +{ + if (!_private->page) + return; + return _private->page->setDefersLoading(defer); +} + +// For backwards compatibility with the WebBackForwardList API, we honor both +// a per-WebView and a per-preferences setting for whether to use the page cache. + +- (BOOL)usesPageCache +{ + return _private->usesPageCache && [[self preferences] usesPageCache]; +} + +- (void)setUsesPageCache:(BOOL)usesPageCache +{ + _private->usesPageCache = usesPageCache; + + // Post a notification so the WebCore settings update. + [[self preferences] _postPreferencesChangesNotification]; +} + +- (void)handleAuthenticationForResource:(id)identifier challenge:(NSURLAuthenticationChallenge *)challenge fromDataSource:(WebDataSource *)dataSource +{ + NSWindow *window = [self hostWindow] ? [self hostWindow] : [self window]; + [[WebPanelAuthenticationHandler sharedHandler] startAuthentication:challenge window:window]; +} + +- (void)_clearUndoRedoOperations +{ + if (!_private->page) + return; + _private->page->clearUndoRedoOperations(); +} + +- (void)_setCatchesDelegateExceptions:(BOOL)f +{ + _private->catchesDelegateExceptions = f; +} + +- (BOOL)_catchesDelegateExceptions +{ + return _private->catchesDelegateExceptions; +} + +@end + +@implementation _WebSafeForwarder + +// Used to send messages to delegates that implement informal protocols. + +- (id)initWithTarget:(id)t defaultTarget:(id)dt catchExceptions:(BOOL)c +{ + self = [super init]; + if (!self) + return nil; + target = t; // Non retained. + defaultTarget = dt; + catchExceptions = c; + return self; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + if ([target respondsToSelector:[invocation selector]]) { + if (catchExceptions) { + @try { + [invocation invokeWithTarget:target]; + } @catch(id exception) { + ReportDiscardedDelegateException([invocation selector], exception); + } + } else + [invocation invokeWithTarget:target]; + return; + } + + if ([defaultTarget respondsToSelector:[invocation selector]]) + [invocation invokeWithTarget:defaultTarget]; + + // Do nothing quietly if method not implemented. +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + return [defaultTarget methodSignatureForSelector:aSelector]; +} + +@end + +@implementation WebView + ++ (void)initialize +{ + static BOOL initialized = NO; + if (initialized) + return; + initialized = YES; + +#ifdef REMOVE_SAFARI_DOM_TREE_DEBUG_ITEM + // This prevents open source users from crashing when using the Show DOM Tree menu item in Safari 2. + // FIXME: remove this when we no longer need to support Safari 2. + if ([[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.apple.Safari"] && [[NSUserDefaults standardUserDefaults] boolForKey:@"IncludeDebugMenu"]) + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_finishedLaunching) name:NSApplicationDidFinishLaunchingNotification object:NSApp]; +#endif + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillTerminate) name:NSApplicationWillTerminateNotification object:NSApp]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_preferencesChangedNotification:) name:WebPreferencesChangedNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_preferencesRemovedNotification:) name:WebPreferencesRemovedNotification object:nil]; +} + ++ (void)_applicationWillTerminate +{ + applicationIsTerminating = YES; + if (!pluginDatabaseClientCount) + [WebPluginDatabase closeSharedDatabase]; +} + +#ifdef REMOVE_SAFARI_DOM_TREE_DEBUG_ITEM +// FIXME: remove this when it is no longer needed to prevent Safari from crashing ++ (void)_finishedLaunching +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_removeDOMTreeMenuItem:) name:NSMenuDidAddItemNotification object:[NSApp mainMenu]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidFinishLaunchingNotification object:NSApp]; +} + ++(void)_removeDOMTreeMenuItem:(NSNotification *)notification +{ + NSMenu *debugMenu = [[[[NSApp mainMenu] itemArray] lastObject] submenu]; + NSMenuItem *domTree = [debugMenu itemWithTitle:@"Show DOM Tree"]; + if (domTree) + [debugMenu removeItem:domTree]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMenuDidAddItemNotification object:[NSApp mainMenu]]; +} +#endif + ++ (BOOL)canShowMIMEType:(NSString *)MIMEType +{ + return [self _viewClass:nil andRepresentationClass:nil forMIMEType:MIMEType]; +} + +- (WebBasePluginPackage *)_pluginForMIMEType:(NSString *)MIMEType +{ + WebBasePluginPackage *pluginPackage = [[WebPluginDatabase sharedDatabase] pluginForMIMEType:MIMEType]; + if (pluginPackage) + return pluginPackage; + + if (_private->pluginDatabase) + return [_private->pluginDatabase pluginForMIMEType:MIMEType]; + + return nil; +} + +- (WebBasePluginPackage *)_pluginForExtension:(NSString *)extension +{ + WebBasePluginPackage *pluginPackage = [[WebPluginDatabase sharedDatabase] pluginForExtension:extension]; + if (pluginPackage) + return pluginPackage; + + if (_private->pluginDatabase) + return [_private->pluginDatabase pluginForExtension:extension]; + + return nil; +} + +- (BOOL)_isMIMETypeRegisteredAsPlugin:(NSString *)MIMEType +{ + if ([[WebPluginDatabase sharedDatabase] isMIMETypeRegistered:MIMEType]) + return YES; + + if (_private->pluginDatabase && [_private->pluginDatabase isMIMETypeRegistered:MIMEType]) + return YES; + + return NO; +} + ++ (BOOL)canShowMIMETypeAsHTML:(NSString *)MIMEType +{ + return [WebFrameView _canShowMIMETypeAsHTML:MIMEType]; +} + ++ (NSArray *)MIMETypesShownAsHTML +{ + NSMutableDictionary *viewTypes = [WebFrameView _viewTypesAllowImageTypeOmission:YES]; + NSEnumerator *enumerator = [viewTypes keyEnumerator]; + id key; + NSMutableArray *array = [[[NSMutableArray alloc] init] autorelease]; + + while ((key = [enumerator nextObject])) { + if ([viewTypes objectForKey:key] == [WebHTMLView class]) + [array addObject:key]; + } + + return array; +} + ++ (void)setMIMETypesShownAsHTML:(NSArray *)MIMETypes +{ + NSDictionary *viewTypes = [[WebFrameView _viewTypesAllowImageTypeOmission:YES] copy]; + NSEnumerator *enumerator = [viewTypes keyEnumerator]; + id key; + while ((key = [enumerator nextObject])) { + if ([viewTypes objectForKey:key] == [WebHTMLView class]) + [WebView _unregisterViewClassAndRepresentationClassForMIMEType:key]; + } + + int i, count = [MIMETypes count]; + for (i = 0; i < count; i++) { + [WebView registerViewClass:[WebHTMLView class] + representationClass:[WebHTMLRepresentation class] + forMIMEType:[MIMETypes objectAtIndex:i]]; + } + [viewTypes release]; +} + ++ (NSURL *)URLFromPasteboard:(NSPasteboard *)pasteboard +{ + return [pasteboard _web_bestURL]; +} + ++ (NSString *)URLTitleFromPasteboard:(NSPasteboard *)pasteboard +{ + return [pasteboard stringForType:WebURLNamePboardType]; +} + ++ (void)registerURLSchemeAsLocal:(NSString *)protocol +{ + FrameLoader::registerURLSchemeAsLocal(protocol); +} + +- (void)_registerDraggedTypes +{ + NSArray *editableTypes = [WebHTMLView _insertablePasteboardTypes]; + NSArray *URLTypes = [NSPasteboard _web_dragTypesForURL]; + NSMutableSet *types = [[NSMutableSet alloc] initWithArray:editableTypes]; + [types addObjectsFromArray:URLTypes]; + [self registerForDraggedTypes:[types allObjects]]; + [types release]; +} + +- (void)_commonInitializationWithFrameName:(NSString *)frameName groupName:(NSString *)groupName +{ + WebPreferences *standardPreferences = [WebPreferences standardPreferences]; + [standardPreferences willAddToWebView]; + + _private->preferences = [standardPreferences retain]; + _private->catchesDelegateExceptions = YES; + _private->mainFrameDocumentReady = NO; + _private->drawsBackground = YES; + _private->smartInsertDeleteEnabled = YES; + _private->backgroundColor = [[NSColor whiteColor] retain]; + + NSRect f = [self frame]; + WebFrameView *frameView = [[WebFrameView alloc] initWithFrame: NSMakeRect(0,0,f.size.width,f.size.height)]; + [frameView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [self addSubview:frameView]; + [frameView release]; + + WebKitInitializeLoggingChannelsIfNecessary(); + WebCore::InitializeLoggingChannelsIfNecessary(); + [WebHistoryItem initWindowWatcherIfNecessary]; + + _private->page = new Page(new WebChromeClient(self), new WebContextMenuClient(self), new WebEditorClient(self), new WebDragClient(self), new WebInspectorClient(self)); + [[[WebFrameBridge alloc] initMainFrameWithPage:_private->page frameName:frameName frameView:frameView] release]; + + [self _addToAllWebViewsSet]; + [self setGroupName:groupName]; + + // If there's already a next key view (e.g., from a nib), wire it up to our + // contained frame view. In any case, wire our next key view up to the our + // contained frame view. This works together with our becomeFirstResponder + // and setNextKeyView overrides. + NSView *nextKeyView = [self nextKeyView]; + if (nextKeyView != nil && nextKeyView != frameView) { + [frameView setNextKeyView:nextKeyView]; + } + [super setNextKeyView:frameView]; + + ++WebViewCount; + + [self _registerDraggedTypes]; + + // initialize WebScriptDebugServer here so listeners can register before any pages are loaded. + if ([WebView _scriptDebuggerEnabled]) + [WebScriptDebugServer sharedScriptDebugServer]; + + WebPreferences *prefs = [self preferences]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_preferencesChangedNotification:) + name:WebPreferencesChangedNotification object:prefs]; + + // Post a notification so the WebCore settings update. + [[self preferences] _postPreferencesChangesNotification]; + + if (WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_LOCAL_RESOURCE_SECURITY_RESTRICTION)) + FrameLoader::setRestrictAccessToLocal(true); +} + +- (id)initWithFrame:(NSRect)f +{ + return [self initWithFrame:f frameName:nil groupName:nil]; +} + +- (id)initWithFrame:(NSRect)f frameName:(NSString *)frameName groupName:(NSString *)groupName; +{ + self = [super initWithFrame:f]; + if (!self) + return nil; + +#ifdef ENABLE_WEBKIT_UNSET_DYLD_FRAMEWORK_PATH + // DYLD_FRAMEWORK_PATH is used so Safari will load the development version of WebKit, which + // may not work with other WebKit applications. Unsetting DYLD_FRAMEWORK_PATH removes the + // need for Safari to unset it to prevent it from being passed to applications it launches. + // Unsetting it when a WebView is first created is as good a place as any. + // See for more details. + if (getenv("WEBKIT_UNSET_DYLD_FRAMEWORK_PATH")) { + unsetenv("DYLD_FRAMEWORK_PATH"); + unsetenv("WEBKIT_UNSET_DYLD_FRAMEWORK_PATH"); + } +#endif + + _private = [[WebViewPrivate alloc] init]; + [self _commonInitializationWithFrameName:frameName groupName:groupName]; + [self setMaintainsBackForwardList: YES]; + return self; +} + +- (id)initWithCoder:(NSCoder *)decoder +{ + WebView *result = nil; + +NS_DURING + + NSString *frameName; + NSString *groupName; + WebPreferences *preferences; + BOOL useBackForwardList = NO; + BOOL allowsUndo = YES; + + result = [super initWithCoder:decoder]; + result->_private = [[WebViewPrivate alloc] init]; + + // We don't want any of the archived subviews. The subviews will always + // be created in _commonInitializationFrameName:groupName:. + [[result subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; + + if ([decoder allowsKeyedCoding]) { + frameName = [decoder decodeObjectForKey:@"FrameName"]; + groupName = [decoder decodeObjectForKey:@"GroupName"]; + preferences = [decoder decodeObjectForKey:@"Preferences"]; + useBackForwardList = [decoder decodeBoolForKey:@"UseBackForwardList"]; + if ([decoder containsValueForKey:@"AllowsUndo"]) + allowsUndo = [decoder decodeBoolForKey:@"AllowsUndo"]; + } else { + int version; + [decoder decodeValueOfObjCType:@encode(int) at:&version]; + frameName = [decoder decodeObject]; + groupName = [decoder decodeObject]; + preferences = [decoder decodeObject]; + if (version > 1) + [decoder decodeValuesOfObjCTypes:"c", &useBackForwardList]; + // The allowsUndo field is no longer written out in encodeWithCoder, but since there are + // version 3 NIBs that have this field encoded, we still need to read it in. + if (version == 3) + [decoder decodeValuesOfObjCTypes:"c", &allowsUndo]; + } + + if (![frameName isKindOfClass:[NSString class]]) + frameName = nil; + if (![groupName isKindOfClass:[NSString class]]) + groupName = nil; + if (![preferences isKindOfClass:[WebPreferences class]]) + preferences = nil; + + LOG(Encoding, "FrameName = %@, GroupName = %@, useBackForwardList = %d\n", frameName, groupName, (int)useBackForwardList); + [result _commonInitializationWithFrameName:frameName groupName:groupName]; + [result page]->backForwardList()->setEnabled(useBackForwardList); + result->_private->allowsUndo = allowsUndo; + if (preferences) + [result setPreferences:preferences]; + +NS_HANDLER + + result = nil; + [self release]; + +NS_ENDHANDLER + + return result; +} + +- (void)encodeWithCoder:(NSCoder *)encoder +{ + // Set asside the subviews before we archive. We don't want to archive any subviews. + // The subviews will always be created in _commonInitializationFrameName:groupName:. + id originalSubviews = _subviews; + _subviews = nil; + + [super encodeWithCoder:encoder]; + + // Restore the subviews we set aside. + _subviews = originalSubviews; + + BOOL useBackForwardList = _private->page && _private->page->backForwardList()->enabled(); + if ([encoder allowsKeyedCoding]) { + [encoder encodeObject:[[self mainFrame] name] forKey:@"FrameName"]; + [encoder encodeObject:[self groupName] forKey:@"GroupName"]; + [encoder encodeObject:[self preferences] forKey:@"Preferences"]; + [encoder encodeBool:useBackForwardList forKey:@"UseBackForwardList"]; + [encoder encodeBool:_private->allowsUndo forKey:@"AllowsUndo"]; + } else { + int version = WebViewVersion; + [encoder encodeValueOfObjCType:@encode(int) at:&version]; + [encoder encodeObject:[[self mainFrame] name]]; + [encoder encodeObject:[self groupName]]; + [encoder encodeObject:[self preferences]]; + [encoder encodeValuesOfObjCTypes:"c", &useBackForwardList]; + // DO NOT encode any new fields here, doing so will break older WebKit releases. + } + + LOG(Encoding, "FrameName = %@, GroupName = %@, useBackForwardList = %d\n", [[self mainFrame] name], [self groupName], (int)useBackForwardList); +} + +- (void)dealloc +{ + // call close to ensure we tear-down completely + // this maintains our old behavior for existing applications + [self _close]; + + --WebViewCount; + + [_private release]; + // [super dealloc] can end up dispatching against _private (3466082) + _private = nil; + + [super dealloc]; +} + +- (void)finalize +{ + ASSERT(_private->closed); + + --WebViewCount; + + [super finalize]; +} + +- (void)close +{ + [self _close]; +} + +- (void)setShouldCloseWithWindow:(BOOL)close +{ + _private->shouldCloseWithWindow = close; +} + +- (BOOL)shouldCloseWithWindow +{ + return _private->shouldCloseWithWindow; +} + +- (void)viewWillMoveToWindow:(NSWindow *)window +{ + // Don't do anything if we aren't initialized. This happens when decoding a WebView. + if (!_private) + return; + + if ([self window]) + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:[self window]]; + + if (window) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowWillClose:) name:NSWindowWillCloseNotification object:window]; + + // Ensure that we will receive the events that WebHTMLView (at least) needs. It's expensive enough + // that we don't want to call it over and over. + [window setAcceptsMouseMovedEvents:YES]; + WKSetNSWindowShouldPostEventNotifications(window, YES); + } +} + +- (void)_windowWillClose:(NSNotification *)notification +{ + if ([self shouldCloseWithWindow] && ([self window] == [self hostWindow] || ([self window] && ![self hostWindow]) || (![self window] && [self hostWindow]))) + [self _close]; +} + +- (void)setPreferences:(WebPreferences *)prefs +{ + if (!prefs) + prefs = [WebPreferences standardPreferences]; + + if (_private->preferences == prefs) + return; + + [prefs willAddToWebView]; + + WebPreferences *oldPrefs = _private->preferences; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:WebPreferencesChangedNotification object:[self preferences]]; + [WebPreferences _removeReferenceForIdentifier:[oldPrefs identifier]]; + + _private->preferences = [prefs retain]; + + // After registering for the notification, post it so the WebCore settings update. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_preferencesChangedNotification:) + name:WebPreferencesChangedNotification object:[self preferences]]; + [[self preferences] _postPreferencesChangesNotification]; + + [oldPrefs didRemoveFromWebView]; + [oldPrefs release]; +} + +- (WebPreferences *)preferences +{ + return _private->preferences; +} + +- (void)setPreferencesIdentifier:(NSString *)anIdentifier +{ + if (!_private->closed && ![anIdentifier isEqual:[[self preferences] identifier]]) { + WebPreferences *prefs = [[WebPreferences alloc] initWithIdentifier:anIdentifier]; + [self setPreferences:prefs]; + [prefs release]; + } +} + +- (NSString *)preferencesIdentifier +{ + return [[self preferences] identifier]; +} + + +- (void)setUIDelegate:delegate +{ + _private->UIDelegate = delegate; + [_private->UIDelegateForwarder release]; + _private->UIDelegateForwarder = nil; +} + +- UIDelegate +{ + return _private->UIDelegate; +} + +- (void)setResourceLoadDelegate: delegate +{ + _private->resourceProgressDelegate = delegate; + [self _cacheResourceLoadDelegateImplementations]; +} + +- resourceLoadDelegate +{ + return _private->resourceProgressDelegate; +} + +- (void)setDownloadDelegate: delegate +{ + _private->downloadDelegate = delegate; +} + + +- downloadDelegate +{ + return _private->downloadDelegate; +} + +- (void)setPolicyDelegate:delegate +{ + _private->policyDelegate = delegate; + [_private->policyDelegateForwarder release]; + _private->policyDelegateForwarder = nil; +} + +- policyDelegate +{ + return _private->policyDelegate; +} + +- (void)setFrameLoadDelegate:delegate +{ + _private->frameLoadDelegate = delegate; + [self _cacheFrameLoadDelegateImplementations]; +} + +- frameLoadDelegate +{ + return _private->frameLoadDelegate; +} + +- (WebFrame *)mainFrame +{ + // This can be called in initialization, before _private has been set up (3465613) + if (!_private) + return nil; + if (!_private->page) + return nil; + return kit(_private->page->mainFrame()); +} + +- (WebFrame *)selectedFrame +{ + // If the first responder is a view in our tree, we get the frame containing the first responder. + // This is faster than searching the frame hierarchy, and will give us a result even in the case + // where the focused frame doesn't actually contain a selection. + WebFrame *focusedFrame = [self _focusedFrame]; + if (focusedFrame) + return focusedFrame; + + // If the first responder is outside of our view tree, we search for a frame containing a selection. + // There should be at most only one of these. + return [[self mainFrame] _findFrameWithSelection]; +} + +- (WebBackForwardList *)backForwardList +{ + if (!_private->page) + return nil; + if (!_private->page->backForwardList()->enabled()) + return nil; + return kit(_private->page->backForwardList()); +} + +- (void)setMaintainsBackForwardList: (BOOL)flag +{ + if (!_private->page) + return; + _private->page->backForwardList()->setEnabled(flag); +} + +- (BOOL)goBack +{ + if (!_private->page) + return NO; + + return _private->page->goBack(); +} + +- (BOOL)goForward +{ + if (!_private->page) + return NO; + + return _private->page->goForward(); +} + +- (BOOL)goToBackForwardItem:(WebHistoryItem *)item +{ + if (!_private->page) + return NO; + + _private->page->goToItem(core(item), FrameLoadTypeIndexedBackForward); + return YES; +} + +- (void)setTextSizeMultiplier:(float)m +{ + // NOTE: This has no visible effect when viewing a PDF (see ) + if (_private->textSizeMultiplier == m) + return; + + _private->textSizeMultiplier = m; + [self _notifyTextSizeMultiplierChanged]; +} + +- (float)textSizeMultiplier +{ + return _private->textSizeMultiplier; +} + +- (void)setApplicationNameForUserAgent:(NSString *)applicationName +{ + NSString *name = [applicationName copy]; + [_private->applicationNameForUserAgent release]; + _private->applicationNameForUserAgent = name; + if (!_private->userAgentOverridden) + *_private->userAgent = String(); +} + +- (NSString *)applicationNameForUserAgent +{ + return [[_private->applicationNameForUserAgent retain] autorelease]; +} + +- (void)setCustomUserAgent:(NSString *)userAgentString +{ + *_private->userAgent = userAgentString; + _private->userAgentOverridden = userAgentString != nil; +} + +- (NSString *)customUserAgent +{ + if (!_private->userAgentOverridden) + return nil; + return *_private->userAgent; +} + +- (void)setMediaStyle:(NSString *)mediaStyle +{ + if (_private->mediaStyle != mediaStyle) { + [_private->mediaStyle release]; + _private->mediaStyle = [mediaStyle copy]; + } +} + +- (NSString *)mediaStyle +{ + return _private->mediaStyle; +} + +- (BOOL)supportsTextEncoding +{ + id documentView = [[[self mainFrame] frameView] documentView]; + return [documentView conformsToProtocol:@protocol(WebDocumentText)] + && [documentView supportsTextEncoding]; +} + +- (void)setCustomTextEncodingName:(NSString *)encoding +{ + NSString *oldEncoding = [self customTextEncodingName]; + if (encoding == oldEncoding || [encoding isEqualToString:oldEncoding]) + return; + FrameLoader* mainFrameLoader = [[self mainFrame] _frameLoader]; + if (mainFrameLoader) + mainFrameLoader->reloadAllowingStaleData(encoding); +} + +- (NSString *)_mainFrameOverrideEncoding +{ + WebDataSource *dataSource = [[self mainFrame] provisionalDataSource]; + if (dataSource == nil) + dataSource = [[self mainFrame] _dataSource]; + if (dataSource == nil) + return nil; + return nsStringNilIfEmpty([dataSource _documentLoader]->overrideEncoding()); +} + +- (NSString *)customTextEncodingName +{ + return [self _mainFrameOverrideEncoding]; +} + +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script +{ + // FIXME: We can remove this workaround for VitalSource Bookshelf when they update + // their code so that it no longer calls stringByEvaluatingJavaScriptFromString with a return statement. + // Return statements are only valid in a function. See for the evangelism bug. + if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_VITALSOURCE_QUIRK) && [[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.vitalsource.bookshelf"]) { + NSRange returnStringRange = [script rangeOfString:@"return "]; + if (returnStringRange.length != 0 && returnStringRange.location == 0) + script = [script substringFromIndex: returnStringRange.location + returnStringRange.length]; + } + + NSString *result = [[[self mainFrame] _bridge] stringByEvaluatingJavaScriptFromString:script]; + // The only way stringByEvaluatingJavaScriptFromString can return nil is if the frame was removed by the script + // Since there's no way to get rid of the main frame, result will never ever be nil here. + ASSERT(result); + + return result; +} + +- (WebScriptObject *)windowScriptObject +{ + Frame* coreFrame = core([self mainFrame]); + if (!coreFrame) + return nil; + return coreFrame->windowScriptObject(); +} + +// Get the appropriate user-agent string for a particular URL. +- (NSString *)userAgentForURL:(NSURL *)url +{ + return [self _userAgentForURL:KURL([url absoluteURL])]; +} + +- (void)setHostWindow:(NSWindow *)hostWindow +{ + if (!_private->closed && hostWindow != _private->hostWindow) { + [[self mainFrame] _viewWillMoveToHostWindow:hostWindow]; + if (_private->hostWindow) + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:_private->hostWindow]; + if (hostWindow) + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowWillClose:) name:NSWindowWillCloseNotification object:hostWindow]; + [_private->hostWindow release]; + _private->hostWindow = [hostWindow retain]; + [[self mainFrame] _viewDidMoveToHostWindow]; + } +} + +- (NSWindow *)hostWindow +{ + return _private->hostWindow; +} + +- (NSView *)documentViewAtWindowPoint:(NSPoint)point +{ + return [[self _frameViewAtWindowPoint:point] documentView]; +} + +- (NSDictionary *)_elementAtWindowPoint:(NSPoint)windowPoint +{ + WebFrameView *frameView = [self _frameViewAtWindowPoint:windowPoint]; + if (!frameView) + return nil; + NSView *documentView = [frameView documentView]; + if ([documentView conformsToProtocol:@protocol(WebDocumentElement)]) { + NSPoint point = [documentView convertPoint:windowPoint fromView:nil]; + return [(NSView *)documentView elementAtPoint:point]; + } + return [NSDictionary dictionaryWithObject:[frameView webFrame] forKey:WebElementFrameKey]; +} + +- (NSDictionary *)elementAtPoint:(NSPoint)point +{ + return [self _elementAtWindowPoint:[self convertPoint:point toView:nil]]; +} + +// The following 2 internal NSView methods are called on the drag destination by make scrolling while dragging work. +// Scrolling while dragging will only work if the drag destination is in a scroll view. The WebView is the drag destination. +// When dragging to a WebView, the document subview should scroll, but it doesn't because it is not the drag destination. +// Forward these calls to the document subview to make its scroll view scroll. +- (void)_autoscrollForDraggingInfo:(id)draggingInfo timeDelta:(NSTimeInterval)repeatDelta +{ + NSView *documentView = [self documentViewAtWindowPoint:[draggingInfo draggingLocation]]; + [documentView _autoscrollForDraggingInfo:draggingInfo timeDelta:repeatDelta]; +} + +- (BOOL)_shouldAutoscrollForDraggingInfo:(id)draggingInfo +{ + NSView *documentView = [self documentViewAtWindowPoint:[draggingInfo draggingLocation]]; + return [documentView _shouldAutoscrollForDraggingInfo:draggingInfo]; +} + +- (NSDragOperation)draggingEntered:(id )draggingInfo +{ + NSView * view = [self documentViewAtWindowPoint:[draggingInfo draggingLocation]]; + WebPasteboardHelper helper([view isKindOfClass:[WebHTMLView class]] ? (WebHTMLView*)view : nil); + IntPoint client([draggingInfo draggingLocation]); + IntPoint global(globalPoint([draggingInfo draggingLocation], [self window])); + DragData dragData(draggingInfo, client, global, (DragOperation)[draggingInfo draggingSourceOperationMask], &helper); + return core(self)->dragController()->dragEntered(&dragData); +} + +- (NSDragOperation)draggingUpdated:(id )draggingInfo +{ + NSView * view = [self documentViewAtWindowPoint:[draggingInfo draggingLocation]]; + WebPasteboardHelper helper([view isKindOfClass:[WebHTMLView class]] ? (WebHTMLView*)view : nil); + IntPoint client([draggingInfo draggingLocation]); + IntPoint global(globalPoint([draggingInfo draggingLocation], [self window])); + DragData dragData(draggingInfo, client, global, (DragOperation)[draggingInfo draggingSourceOperationMask], &helper); + return core(self)->dragController()->dragUpdated(&dragData); +} + +- (void)draggingExited:(id )draggingInfo +{ + NSView * view = [self documentViewAtWindowPoint:[draggingInfo draggingLocation]]; + WebPasteboardHelper helper([view isKindOfClass:[WebHTMLView class]] ? (WebHTMLView*)view : nil); + IntPoint client([draggingInfo draggingLocation]); + IntPoint global(globalPoint([draggingInfo draggingLocation], [self window])); + DragData dragData(draggingInfo, client, global, (DragOperation)[draggingInfo draggingSourceOperationMask], &helper); + core(self)->dragController()->dragExited(&dragData); +} + +- (BOOL)prepareForDragOperation:(id )draggingInfo +{ + return YES; +} + +- (BOOL)performDragOperation:(id )draggingInfo +{ + NSView * view = [self documentViewAtWindowPoint:[draggingInfo draggingLocation]]; + WebPasteboardHelper helper([view isKindOfClass:[WebHTMLView class]]? (WebHTMLView*)view : nil); + IntPoint client([draggingInfo draggingLocation]); + IntPoint global(globalPoint([draggingInfo draggingLocation], [self window])); + DragData dragData(draggingInfo, client, global, (DragOperation)[draggingInfo draggingSourceOperationMask], &helper); + return core(self)->dragController()->performDrag(&dragData); +} + +- (NSView *)_hitTest:(NSPoint *)aPoint dragTypes:(NSSet *)types +{ + NSView *hitView = [super _hitTest:aPoint dragTypes:types]; + if (!hitView && [[self superview] mouse:*aPoint inRect:[self frame]]) { + return self; + } else { + return hitView; + } +} + +- (BOOL)acceptsFirstResponder +{ + return [[[self mainFrame] frameView] acceptsFirstResponder]; +} + +- (BOOL)becomeFirstResponder +{ + if (_private->becomingFirstResponder) { + // Fix for unrepro infinite recursion reported in radar 4448181. If we hit this assert on + // a debug build, we should figure out what causes the problem and do a better fix. + ASSERT_NOT_REACHED(); + return NO; + } + + // This works together with setNextKeyView to splice the WebView into + // the key loop similar to the way NSScrollView does this. Note that + // WebFrameView has very similar code. + NSWindow *window = [self window]; + WebFrameView *mainFrameView = [[self mainFrame] frameView]; + + NSResponder *previousFirstResponder = [[self window] _oldFirstResponderBeforeBecoming]; + BOOL fromOutside = ![previousFirstResponder isKindOfClass:[NSView class]] || (![(NSView *)previousFirstResponder isDescendantOf:self] && previousFirstResponder != self); + + if ([window keyViewSelectionDirection] == NSSelectingPrevious) { + NSView *previousValidKeyView = [self previousValidKeyView]; + if ((previousValidKeyView != self) && (previousValidKeyView != mainFrameView)) { + _private->becomingFirstResponder = YES; + _private->becomingFirstResponderFromOutside = fromOutside; + [window makeFirstResponder:previousValidKeyView]; + _private->becomingFirstResponderFromOutside = NO; + _private->becomingFirstResponder = NO; + return YES; + } else { + return NO; + } + } + + if ([mainFrameView acceptsFirstResponder]) { + _private->becomingFirstResponder = YES; + _private->becomingFirstResponderFromOutside = fromOutside; + [window makeFirstResponder:mainFrameView]; + _private->becomingFirstResponderFromOutside = NO; + _private->becomingFirstResponder = NO; + return YES; + } + + return NO; +} + +- (NSView *)_webcore_effectiveFirstResponder +{ + WebFrameView *frameView = [[self mainFrame] frameView]; + return frameView ? [frameView _webcore_effectiveFirstResponder] : [super _webcore_effectiveFirstResponder]; +} + +- (void)setNextKeyView:(NSView *)aView +{ + // This works together with becomeFirstResponder to splice the WebView into + // the key loop similar to the way NSScrollView does this. Note that + // WebFrameView has very similar code. + WebFrameView *mainFrameView = [[self mainFrame] frameView]; + if (mainFrameView != nil) { + [mainFrameView setNextKeyView:aView]; + } else { + [super setNextKeyView:aView]; + } +} + +static WebFrame *incrementFrame(WebFrame *curr, BOOL forward, BOOL wrapFlag) +{ + Frame* coreFrame = core(curr); + return kit(forward + ? coreFrame->tree()->traverseNextWithWrap(wrapFlag) + : coreFrame->tree()->traversePreviousWithWrap(wrapFlag)); +} + +- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag +{ + return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO]; +} + ++ (void)registerViewClass:(Class)viewClass representationClass:(Class)representationClass forMIMEType:(NSString *)MIMEType +{ + [[WebFrameView _viewTypesAllowImageTypeOmission:YES] setObject:viewClass forKey:MIMEType]; + [[WebDataSource _repTypesAllowImageTypeOmission:YES] setObject:representationClass forKey:MIMEType]; + + // FIXME: We also need to maintain MIMEType registrations (which can be dynamically changed) + // in the WebCore MIMEType registry. For now we're doing this in a safe, limited manner + // to fix - a future revamping of the entire system is neccesary for future robustness + if ([viewClass class] == [WebHTMLView class]) + MIMETypeRegistry::getSupportedNonImageMIMETypes().add(MIMEType); +} + +- (void)setGroupName:(NSString *)groupName +{ + if (!_private->page) + return; + _private->page->setGroupName(groupName); +} + +- (NSString *)groupName +{ + if (!_private->page) + return nil; + return _private->page->groupName(); +} + +- (double)estimatedProgress +{ + if (!_private->page) + return 0.0; + + return _private->page->progress()->estimatedProgress(); +} + +- (NSArray *)pasteboardTypesForSelection +{ + NSView *documentView = [[[self _selectedOrMainFrame] frameView] documentView]; + if ([documentView conformsToProtocol:@protocol(WebDocumentSelection)]) { + return [(NSView *)documentView pasteboardTypesForSelection]; + } + return [NSArray array]; +} + +- (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard +{ + WebFrame *frame = [self _selectedOrMainFrame]; + if (frame && [frame _hasSelection]) { + NSView *documentView = [[frame frameView] documentView]; + if ([documentView conformsToProtocol:@protocol(WebDocumentSelection)]) + [(NSView *)documentView writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard]; + } +} + +- (NSArray *)pasteboardTypesForElement:(NSDictionary *)element +{ + if ([element objectForKey:WebElementImageURLKey] != nil) { + return [NSPasteboard _web_writableTypesForImageIncludingArchive:([element objectForKey:WebElementDOMNodeKey] != nil)]; + } else if ([element objectForKey:WebElementLinkURLKey] != nil) { + return [NSPasteboard _web_writableTypesForURL]; + } else if ([[element objectForKey:WebElementIsSelectedKey] boolValue]) { + return [self pasteboardTypesForSelection]; + } + return [NSArray array]; +} + +- (void)writeElement:(NSDictionary *)element withPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard +{ + if ([element objectForKey:WebElementImageURLKey] != nil) { + [self _writeImageForElement:element withPasteboardTypes:types toPasteboard:pasteboard]; + } else if ([element objectForKey:WebElementLinkURLKey] != nil) { + [self _writeLinkElement:element withPasteboardTypes:types toPasteboard:pasteboard]; + } else if ([[element objectForKey:WebElementIsSelectedKey] boolValue]) { + [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard]; + } +} + +- (void)moveDragCaretToPoint:(NSPoint)point +{ + if (Page* page = core(self)) + page->dragController()->placeDragCaret(IntPoint([self convertPoint:point toView:nil])); +} + +- (void)removeDragCaret +{ + if (Page* page = core(self)) + page->dragController()->dragEnded(); +} + +- (void)setMainFrameURL:(NSString *)URLString +{ + [[self mainFrame] loadRequest: [NSURLRequest requestWithURL: [NSURL _web_URLWithDataAsString: URLString]]]; +} + +- (NSString *)mainFrameURL +{ + WebDataSource *ds; + ds = [[self mainFrame] provisionalDataSource]; + if (!ds) + ds = [[self mainFrame] _dataSource]; + return [[[ds request] URL] _web_originalDataAsString]; +} + +- (BOOL)isLoading +{ + LOG (Bindings, "isLoading = %d", (int)[self _isLoading]); + return [self _isLoading]; +} + +- (NSString *)mainFrameTitle +{ + NSString *mainFrameTitle = [[[self mainFrame] _dataSource] pageTitle]; + return (mainFrameTitle != nil) ? mainFrameTitle : (NSString *)@""; +} + +- (NSImage *)mainFrameIcon +{ + return [[WebIconDatabase sharedIconDatabase] iconForURL:[[[[self mainFrame] _dataSource] _URL] _web_originalDataAsString] withSize:WebIconSmallSize]; +} + +- (DOMDocument *)mainFrameDocument +{ + // only return the actual value if the state we're in gives NSTreeController + // enough time to release its observers on the old model + if (_private->mainFrameDocumentReady) + return [[self mainFrame] DOMDocument]; + return nil; +} + +- (void)setDrawsBackground:(BOOL)drawsBackground +{ + if (_private->drawsBackground == drawsBackground) + return; + _private->drawsBackground = drawsBackground; + [[self mainFrame] _updateBackground]; +} + +- (BOOL)drawsBackground +{ + return _private->drawsBackground; +} + +@end + +@implementation WebView (WebIBActions) + +- (IBAction)takeStringURLFrom: sender +{ + NSString *URLString = [sender stringValue]; + + [[self mainFrame] loadRequest: [NSURLRequest requestWithURL: [NSURL _web_URLWithDataAsString: URLString]]]; +} + +- (BOOL)canGoBack +{ + if (!_private->page) + return NO; + + return !!_private->page->backForwardList()->backItem(); +} + +- (BOOL)canGoForward +{ + if (!_private->page) + return NO; + + return !!_private->page->backForwardList()->forwardItem(); +} + +- (IBAction)goBack:(id)sender +{ + [self goBack]; +} + +- (IBAction)goForward:(id)sender +{ + [self goForward]; +} + +- (IBAction)stopLoading:(id)sender +{ + [[self mainFrame] stopLoading]; +} + +- (IBAction)reload:(id)sender +{ + [[self mainFrame] reload]; +} + +#define MinimumTextSizeMultiplier 0.5f +#define MaximumTextSizeMultiplier 3.0f +#define TextSizeMultiplierRatio 1.2f + +- (BOOL)canMakeTextSmaller +{ + BOOL canShrinkMore = _private->textSizeMultiplier/TextSizeMultiplierRatio > MinimumTextSizeMultiplier; + return [self _performTextSizingSelector:(SEL)0 withObject:nil onTrackingDocs:canShrinkMore selForNonTrackingDocs:@selector(_canMakeTextSmaller) newScaleFactor:0]; +} + +- (BOOL)canMakeTextLarger +{ + BOOL canGrowMore = _private->textSizeMultiplier*TextSizeMultiplierRatio < MaximumTextSizeMultiplier; + return [self _performTextSizingSelector:(SEL)0 withObject:nil onTrackingDocs:canGrowMore selForNonTrackingDocs:@selector(_canMakeTextLarger) newScaleFactor:0]; +} + +- (IBAction)makeTextSmaller:(id)sender +{ + float newScale = _private->textSizeMultiplier / TextSizeMultiplierRatio; + BOOL canShrinkMore = newScale > MinimumTextSizeMultiplier; + [self _performTextSizingSelector:@selector(_makeTextSmaller:) withObject:sender onTrackingDocs:canShrinkMore selForNonTrackingDocs:@selector(_canMakeTextSmaller) newScaleFactor:newScale]; +} + +- (IBAction)makeTextLarger:(id)sender +{ + float newScale = _private->textSizeMultiplier*TextSizeMultiplierRatio; + BOOL canGrowMore = newScale < MaximumTextSizeMultiplier; + [self _performTextSizingSelector:@selector(_makeTextLarger:) withObject:sender onTrackingDocs:canGrowMore selForNonTrackingDocs:@selector(_canMakeTextLarger) newScaleFactor:newScale]; +} + +- (IBAction)toggleSmartInsertDelete:(id)sender +{ + [self setSmartInsertDeleteEnabled:![self smartInsertDeleteEnabled]]; +} + +- (IBAction)toggleContinuousSpellChecking:(id)sender +{ + [self setContinuousSpellCheckingEnabled:![self isContinuousSpellCheckingEnabled]]; +} + +- (BOOL)_responderValidateUserInterfaceItem:(id )item +{ + id responder = [self _responderForResponderOperations]; + if (responder != self && [responder respondsToSelector:[item action]]) { + if ([responder respondsToSelector:@selector(validateUserInterfaceItemWithoutDelegate:)]) + return [responder validateUserInterfaceItemWithoutDelegate:item]; + if ([responder respondsToSelector:@selector(validateUserInterfaceItem:)]) + return [responder validateUserInterfaceItem:item]; + return YES; + } + return NO; +} + +- (BOOL)canMakeTextStandardSize +{ + BOOL notAlreadyStandard = _private->textSizeMultiplier != 1.0f; + return [self _performTextSizingSelector:(SEL)0 withObject:nil onTrackingDocs:notAlreadyStandard selForNonTrackingDocs:@selector(_canMakeTextStandardSize) newScaleFactor:0.0f]; +} + +- (IBAction)makeTextStandardSize:(id)sender +{ + BOOL notAlreadyStandard = _private->textSizeMultiplier != 1.0f; + [self _performTextSizingSelector:@selector(_makeTextStandardSize:) withObject:sender onTrackingDocs:notAlreadyStandard selForNonTrackingDocs:@selector(_canMakeTextStandardSize) newScaleFactor:1.0f]; +} + +#define VALIDATE(name) \ + else if (action == @selector(name:)) { return [self _responderValidateUserInterfaceItem:item]; } + +- (BOOL)validateUserInterfaceItemWithoutDelegate:(id )item +{ + SEL action = [item action]; + + if (action == @selector(goBack:)) { + return [self canGoBack]; + } else if (action == @selector(goForward:)) { + return [self canGoForward]; + } else if (action == @selector(makeTextLarger:)) { + return [self canMakeTextLarger]; + } else if (action == @selector(makeTextSmaller:)) { + return [self canMakeTextSmaller]; + } else if (action == @selector(makeTextStandardSize:)) { + return [self canMakeTextStandardSize]; + } else if (action == @selector(reload:)) { + return [[self mainFrame] _dataSource] != nil; + } else if (action == @selector(stopLoading:)) { + return [self _isLoading]; + } else if (action == @selector(toggleContinuousSpellChecking:)) { + BOOL checkMark = NO; + BOOL retVal = NO; + if ([self _continuousCheckingAllowed]) { + checkMark = [self isContinuousSpellCheckingEnabled]; + retVal = YES; + } + if ([(NSObject *)item isKindOfClass:[NSMenuItem class]]) { + NSMenuItem *menuItem = (NSMenuItem *)item; + [menuItem setState:checkMark ? NSOnState : NSOffState]; + } + return retVal; +#ifndef BUILDING_ON_TIGER + } else if (action == @selector(toggleGrammarChecking:)) { + BOOL checkMark = [self isGrammarCheckingEnabled]; + if ([(NSObject *)item isKindOfClass:[NSMenuItem class]]) { + NSMenuItem *menuItem = (NSMenuItem *)item; + [menuItem setState:checkMark ? NSOnState : NSOffState]; + } + return YES; +#endif + } + FOR_EACH_RESPONDER_SELECTOR(VALIDATE) + + return YES; +} + +- (BOOL)validateUserInterfaceItem:(id )item +{ + BOOL result = [self validateUserInterfaceItemWithoutDelegate:item]; + return CallUIDelegateReturningBoolean(result, self, @selector(webView:validateUserInterfaceItem:defaultValidation:), item, result); +} + +@end + +@implementation WebView (WebPendingPublic) + +- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection +{ + if (_private->closed) + return NO; + + // Get the frame holding the selection, or start with the main frame + WebFrame *startFrame = [self _selectedOrMainFrame]; + + // Search the first frame, then all the other frames, in order + NSView *startSearchView = nil; + WebFrame *frame = startFrame; + do { + WebFrame *nextFrame = incrementFrame(frame, forward, wrapFlag); + + BOOL onlyOneFrame = (frame == nextFrame); + ASSERT(!onlyOneFrame || frame == startFrame); + + id view = [[frame frameView] documentView]; + if ([view conformsToProtocol:@protocol(WebDocumentSearching)]) { + NSView *searchView = (NSView *)view; + + if (frame == startFrame) + startSearchView = searchView; + + BOOL foundString; + // In some cases we have to search some content twice; see comment later in this method. + // We can avoid ever doing this in the common one-frame case by passing YES for wrapFlag + // here, and then bailing out before we get to the code that would search again in the + // same content. + BOOL wrapOnThisPass = wrapFlag && onlyOneFrame; + if ([searchView conformsToProtocol:@protocol(WebDocumentIncrementalSearching)]) + foundString = [(NSView *)searchView searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapOnThisPass startInSelection:startInSelection]; + else + foundString = [searchView searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapOnThisPass]; + + if (foundString) { + if (frame != startFrame) + [startFrame _clearSelection]; + [[self window] makeFirstResponder:searchView]; + return YES; + } + + if (onlyOneFrame) + return NO; + } + frame = nextFrame; + } while (frame && frame != startFrame); + + // If there are multiple frames and wrapFlag is true and we've visited each one without finding a result, we still need to search in the + // first-searched frame up to the selection. However, the API doesn't provide a way to search only up to a particular point. The only + // way to make sure the entire frame is searched is to pass YES for the wrapFlag. When there are no matches, this will search again + // some content that we already searched on the first pass. In the worst case, we could search the entire contents of this frame twice. + // To fix this, we'd need to add a mechanism to specify a range in which to search. + if (wrapFlag && startSearchView) { + BOOL foundString; + if ([startSearchView conformsToProtocol:@protocol(WebDocumentIncrementalSearching)]) + foundString = [(NSView *)startSearchView searchFor:string direction:forward caseSensitive:caseFlag wrap:YES startInSelection:startInSelection]; + else + foundString = [startSearchView searchFor:string direction:forward caseSensitive:caseFlag wrap:YES]; + if (foundString) { + [[self window] makeFirstResponder:startSearchView]; + return YES; + } + } + return NO; +} + +- (void)setHoverFeedbackSuspended:(BOOL)newValue +{ + if (_private->hoverFeedbackSuspended == newValue) + return; + + _private->hoverFeedbackSuspended = newValue; + id documentView = [[[self mainFrame] frameView] documentView]; + // FIXME: in a perfect world we'd do this in a general way that worked with any document view, + // such as by calling a protocol method or using respondsToSelector or sending a notification. + // But until there is any need for these more general solutions, we'll just hardwire it to work + // with WebHTMLView. + // Note that _hoverFeedbackSuspendedChanged needs to be called only on the main WebHTMLView, not + // on each subframe separately. + if ([documentView isKindOfClass:[WebHTMLView class]]) + [(WebHTMLView *)documentView _hoverFeedbackSuspendedChanged]; +} + +- (BOOL)isHoverFeedbackSuspended +{ + return _private->hoverFeedbackSuspended; +} + +- (void)setMainFrameDocumentReady:(BOOL)mainFrameDocumentReady +{ + // by setting this to NO, calls to mainFrameDocument are forced to return nil + // setting this to YES lets it return the actual DOMDocument value + // we use this to tell NSTreeController to reset its observers and clear its state + if (_private->mainFrameDocumentReady == mainFrameDocumentReady) + return; + [self _willChangeValueForKey:_WebMainFrameDocumentKey]; + _private->mainFrameDocumentReady = mainFrameDocumentReady; + [self _didChangeValueForKey:_WebMainFrameDocumentKey]; + // this will cause observers to call mainFrameDocument where this flag will be checked +} + +// This method name is used by Mail on Tiger (but not post-Tiger), so we shouldn't delete it +// until the day comes when we're no longer supporting Mail on Tiger. +- (WebFrame *)_frameForCurrentSelection +{ + return [self _selectedOrMainFrame]; +} + +- (void)setTabKeyCyclesThroughElements:(BOOL)cyclesElements +{ + _private->tabKeyCyclesThroughElementsChanged = YES; + if (_private->page) + _private->page->setTabKeyCyclesThroughElements(cyclesElements); +} + +- (BOOL)tabKeyCyclesThroughElements +{ + return _private->page && _private->page->tabKeyCyclesThroughElements(); +} + +- (void)setScriptDebugDelegate:(id)delegate +{ + _private->scriptDebugDelegate = delegate; + [_private->scriptDebugDelegateForwarder release]; + _private->scriptDebugDelegateForwarder = nil; + if (delegate) + [self _attachScriptDebuggerToAllFrames]; + else + [self _detachScriptDebuggerFromAllFrames]; +} + +- (id)scriptDebugDelegate +{ + return _private->scriptDebugDelegate; +} + +- (BOOL)shouldClose +{ + Frame* coreFrame = core([self mainFrame]); + if (!coreFrame) + return YES; + return coreFrame->shouldClose(); +} + +- (NSAppleEventDescriptor *)aeDescByEvaluatingJavaScriptFromString:(NSString *)script +{ + return [[[self mainFrame] _bridge] aeDescByEvaluatingJavaScriptFromString:script]; +} + +- (BOOL)canMarkAllTextMatches +{ + WebFrame *frame = [self mainFrame]; + do { + id view = [[frame frameView] documentView]; + if (view && ![view conformsToProtocol:@protocol(WebMultipleTextMatches)]) + return NO; + + frame = incrementFrame(frame, YES, NO); + } while (frame); + + return YES; +} + +- (NSUInteger)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag highlight:(BOOL)highlight limit:(NSUInteger)limit +{ + WebFrame *frame = [self mainFrame]; + unsigned matchCount = 0; + do { + id view = [[frame frameView] documentView]; + if ([view conformsToProtocol:@protocol(WebMultipleTextMatches)]) { + [(NSView *)view setMarkedTextMatchesAreHighlighted:highlight]; + + ASSERT(limit == 0 || matchCount < limit); + matchCount += [(NSView *)view markAllMatchesForText:string caseSensitive:caseFlag limit:limit == 0 ? 0 : limit - matchCount]; + + // Stop looking if we've reached the limit. A limit of 0 means no limit. + if (limit > 0 && matchCount >= limit) + break; + } + + frame = incrementFrame(frame, YES, NO); + } while (frame); + + return matchCount; +} + +- (void)unmarkAllTextMatches +{ + WebFrame *frame = [self mainFrame]; + do { + id view = [[frame frameView] documentView]; + if ([view conformsToProtocol:@protocol(WebMultipleTextMatches)]) + [(NSView *)view unmarkAllTextMatches]; + + frame = incrementFrame(frame, YES, NO); + } while (frame); +} + +- (NSArray *)rectsForTextMatches +{ + NSMutableArray *result = [NSMutableArray array]; + WebFrame *frame = [self mainFrame]; + do { + id view = [[frame frameView] documentView]; + if ([view conformsToProtocol:@protocol(WebMultipleTextMatches)]) { + NSView *documentView = (NSView *)view; + NSRect documentViewVisibleRect = [documentView visibleRect]; + NSArray *originalRects = [documentView rectsForTextMatches]; + unsigned rectCount = [originalRects count]; + unsigned rectIndex; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + for (rectIndex = 0; rectIndex < rectCount; ++rectIndex) { + NSRect r = [[originalRects objectAtIndex:rectIndex] rectValue]; + // Clip rect to document view's visible rect so rect is confined to subframe + r = NSIntersectionRect(r, documentViewVisibleRect); + if (NSIsEmptyRect(r)) + continue; + + // Convert rect to our coordinate system + r = [documentView convertRect:r toView:self]; + [result addObject:[NSValue valueWithRect:r]]; + if (rectIndex % 10 == 0) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + } + } + [pool drain]; + } + + frame = incrementFrame(frame, YES, NO); + } while (frame); + + return result; +} + +- (void)scrollDOMRangeToVisible:(DOMRange *)range +{ + [[[range startContainer] _bridge] scrollDOMRangeToVisible:range]; +} + +- (BOOL)allowsUndo +{ + return _private->allowsUndo; +} + +- (void)setAllowsUndo:(BOOL)flag +{ + _private->allowsUndo = flag; +} + +@end + +@implementation WebView (WebViewPrintingPrivate) + +- (float)_headerHeight +{ + return CallUIDelegateReturningFloat(self, @selector(webViewHeaderHeight:)); +} + +- (float)_footerHeight +{ + return CallUIDelegateReturningFloat(self, @selector(webViewFooterHeight:)); +} + +- (void)_drawHeaderInRect:(NSRect)rect +{ +#ifdef DEBUG_HEADER_AND_FOOTER + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + [currentContext saveGraphicsState]; + [[NSColor yellowColor] set]; + NSRectFill(rect); + [currentContext restoreGraphicsState]; +#endif + + SEL selector = @selector(webView:drawHeaderInRect:); + if (![_private->UIDelegate respondsToSelector:selector]) + return; + + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + [currentContext saveGraphicsState]; + + NSRectClip(rect); + CallUIDelegate(self, selector, rect); + + [currentContext restoreGraphicsState]; +} + +- (void)_drawFooterInRect:(NSRect)rect +{ +#ifdef DEBUG_HEADER_AND_FOOTER + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + [currentContext saveGraphicsState]; + [[NSColor cyanColor] set]; + NSRectFill(rect); + [currentContext restoreGraphicsState]; +#endif + + SEL selector = @selector(webView:drawFooterInRect:); + if (![_private->UIDelegate respondsToSelector:selector]) + return; + + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + [currentContext saveGraphicsState]; + + NSRectClip(rect); + CallUIDelegate(self, selector, rect); + + [currentContext restoreGraphicsState]; +} + +- (void)_adjustPrintingMarginsForHeaderAndFooter +{ + NSPrintOperation *op = [NSPrintOperation currentOperation]; + NSPrintInfo *info = [op printInfo]; + NSMutableDictionary *infoDictionary = [info dictionary]; + + // We need to modify the top and bottom margins in the NSPrintInfo to account for the space needed by the + // header and footer. Because this method can be called more than once on the same NSPrintInfo (see 5038087), + // we stash away the unmodified top and bottom margins the first time this method is called, and we read from + // those stashed-away values on subsequent calls. + float originalTopMargin; + float originalBottomMargin; + NSNumber *originalTopMarginNumber = [infoDictionary objectForKey:WebKitOriginalTopPrintingMarginKey]; + if (!originalTopMarginNumber) { + ASSERT(![infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey]); + originalTopMargin = [info topMargin]; + originalBottomMargin = [info bottomMargin]; + [infoDictionary setObject:[NSNumber numberWithFloat:originalTopMargin] forKey:WebKitOriginalTopPrintingMarginKey]; + [infoDictionary setObject:[NSNumber numberWithFloat:originalBottomMargin] forKey:WebKitOriginalBottomPrintingMarginKey]; + } else { + ASSERT([originalTopMarginNumber isKindOfClass:[NSNumber class]]); + ASSERT([[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] isKindOfClass:[NSNumber class]]); + originalTopMargin = [originalTopMarginNumber floatValue]; + originalBottomMargin = [[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] floatValue]; + } + + float scale = [op _web_pageSetupScaleFactor]; + [info setTopMargin:originalTopMargin + [self _headerHeight] * scale]; + [info setBottomMargin:originalBottomMargin + [self _footerHeight] * scale]; +} + +- (void)_drawHeaderAndFooter +{ + // The header and footer rect height scales with the page, but the width is always + // all the way across the printed page (inset by printing margins). + NSPrintOperation *op = [NSPrintOperation currentOperation]; + float scale = [op _web_pageSetupScaleFactor]; + NSPrintInfo *printInfo = [op printInfo]; + NSSize paperSize = [printInfo paperSize]; + float headerFooterLeft = [printInfo leftMargin]/scale; + float headerFooterWidth = (paperSize.width - ([printInfo leftMargin] + [printInfo rightMargin]))/scale; + NSRect footerRect = NSMakeRect(headerFooterLeft, [printInfo bottomMargin]/scale - [self _footerHeight] , + headerFooterWidth, [self _footerHeight]); + NSRect headerRect = NSMakeRect(headerFooterLeft, (paperSize.height - [printInfo topMargin])/scale, + headerFooterWidth, [self _headerHeight]); + + [self _drawHeaderInRect:headerRect]; + [self _drawFooterInRect:footerRect]; +} +@end + +@implementation WebView (WebDebugBinding) + +- (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context +{ + LOG (Bindings, "addObserver:%p forKeyPath:%@ options:%x context:%p", anObserver, keyPath, options, context); + [super addObserver:anObserver forKeyPath:keyPath options:options context:context]; +} + +- (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath +{ + LOG (Bindings, "removeObserver:%p forKeyPath:%@", anObserver, keyPath); + [super removeObserver:anObserver forKeyPath:keyPath]; +} + +@end + +//========================================================================================== +// Editing + +@implementation WebView (WebViewCSS) + +- (DOMCSSStyleDeclaration *)computedStyleForElement:(DOMElement *)element pseudoElement:(NSString *)pseudoElement +{ + // FIXME: is this the best level for this conversion? + if (pseudoElement == nil) { + pseudoElement = @""; + } + return [[element ownerDocument] getComputedStyle:element pseudoElement:pseudoElement]; +} + +@end + +@implementation WebView (WebViewEditing) + +- (DOMRange *)editableDOMRangeForPoint:(NSPoint)point +{ + Page* page = core(self); + if (!page) + return nil; + return kit(page->mainFrame()->editor()->rangeForPoint(IntPoint([self convertPoint:point toView:nil])).get()); +} + +- (BOOL)_shouldChangeSelectedDOMRange:(DOMRange *)currentRange toDOMRange:(DOMRange *)proposedRange affinity:(NSSelectionAffinity)selectionAffinity stillSelecting:(BOOL)flag; +{ + // FIXME: This quirk is needed due to - We can phase it out once Aperture can adopt the new behavior on their end + if (!WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_APERTURE_QUIRK) && [[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.apple.Aperture"]) + return YES; + return [[self _editingDelegateForwarder] webView:self shouldChangeSelectedDOMRange:currentRange toDOMRange:proposedRange affinity:selectionAffinity stillSelecting:flag]; +} + +- (BOOL)maintainsInactiveSelection +{ + return NO; +} + +- (void)setSelectedDOMRange:(DOMRange *)range affinity:(NSSelectionAffinity)selectionAffinity +{ + Frame* coreFrame = core([self _selectedOrMainFrame]); + if (!coreFrame) + return; + + if (range == nil) + coreFrame->selectionController()->clear(); + else { + // Derive the frame to use from the range passed in. + // Using _bridgeForSelectedOrMainFrame could give us a different document than + // the one the range uses. + coreFrame = core([range startContainer])->document()->frame(); + if (!coreFrame) + return; + + ExceptionCode ec = 0; + coreFrame->selectionController()->setSelectedRange([range _range], core(selectionAffinity), true, ec); + } +} + +- (DOMRange *)selectedDOMRange +{ + Frame* coreFrame = core([self _selectedOrMainFrame]); + if (!coreFrame) + return nil; + return kit(coreFrame->selectionController()->toRange().get()); +} + +- (NSSelectionAffinity)selectionAffinity +{ + Frame* coreFrame = core([self _selectedOrMainFrame]); + if (!coreFrame) + return NSSelectionAffinityDownstream; + return kit(coreFrame->selectionController()->affinity()); +} + +- (void)setEditable:(BOOL)flag +{ + if (_private->editable != flag) { + _private->editable = flag; + if (!_private->tabKeyCyclesThroughElementsChanged && _private->page) + _private->page->setTabKeyCyclesThroughElements(!flag); + Frame* mainFrame = [[[self mainFrame] _bridge] _frame]; + if (mainFrame) { + if (flag) { + mainFrame->applyEditingStyleToBodyElement(); + // If the WebView is made editable and the selection is empty, set it to something. + if (![self selectedDOMRange]) + mainFrame->setSelectionFromNone(); + } else + mainFrame->removeEditingStyleFromBodyElement(); + } + } +} + +- (BOOL)isEditable +{ + return _private->editable; +} + +- (void)setTypingStyle:(DOMCSSStyleDeclaration *)style +{ + // We don't know enough at thls level to pass in a relevant WebUndoAction; we'd have to + // change the API to allow this. + [[self _bridgeForSelectedOrMainFrame] setTypingStyle:style withUndoAction:EditActionUnspecified]; +} + +- (DOMCSSStyleDeclaration *)typingStyle +{ + return [[self _bridgeForSelectedOrMainFrame] typingStyle]; +} + +- (void)setSmartInsertDeleteEnabled:(BOOL)flag +{ + _private->smartInsertDeleteEnabled = flag; +} + +- (BOOL)smartInsertDeleteEnabled +{ + return _private->smartInsertDeleteEnabled; +} + +- (void)setContinuousSpellCheckingEnabled:(BOOL)flag +{ + if (continuousSpellCheckingEnabled != flag) { + continuousSpellCheckingEnabled = flag; + [[NSUserDefaults standardUserDefaults] setBool:continuousSpellCheckingEnabled forKey:WebContinuousSpellCheckingEnabled]; + } + + if ([self isContinuousSpellCheckingEnabled]) { + [[self class] _preflightSpellChecker]; + } else { + [[self mainFrame] _unmarkAllMisspellings]; + } +} + +- (BOOL)isContinuousSpellCheckingEnabled +{ + return (continuousSpellCheckingEnabled && [self _continuousCheckingAllowed]); +} + +- (NSInteger)spellCheckerDocumentTag +{ + if (!_private->hasSpellCheckerDocumentTag) { + _private->spellCheckerDocumentTag = [NSSpellChecker uniqueSpellDocumentTag]; + _private->hasSpellCheckerDocumentTag = YES; + } + return _private->spellCheckerDocumentTag; +} + +- (NSUndoManager *)undoManager +{ + if (!_private->allowsUndo) + return nil; + + NSUndoManager *undoManager = [[self _editingDelegateForwarder] undoManagerForWebView:self]; + if (undoManager) + return undoManager; + + return [super undoManager]; +} + +- (void)registerForEditingDelegateNotification:(NSString *)name selector:(SEL)selector +{ + NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; + if ([_private->editingDelegate respondsToSelector:selector]) + [defaultCenter addObserver:_private->editingDelegate selector:selector name:name object:self]; +} + +- (void)setEditingDelegate:(id)delegate +{ + if (_private->editingDelegate == delegate) + return; + + NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; + + // remove notifications from current delegate + [defaultCenter removeObserver:_private->editingDelegate name:WebViewDidBeginEditingNotification object:self]; + [defaultCenter removeObserver:_private->editingDelegate name:WebViewDidChangeNotification object:self]; + [defaultCenter removeObserver:_private->editingDelegate name:WebViewDidEndEditingNotification object:self]; + [defaultCenter removeObserver:_private->editingDelegate name:WebViewDidChangeTypingStyleNotification object:self]; + [defaultCenter removeObserver:_private->editingDelegate name:WebViewDidChangeSelectionNotification object:self]; + + _private->editingDelegate = delegate; + [_private->editingDelegateForwarder release]; + _private->editingDelegateForwarder = nil; + + // add notifications for new delegate + [self registerForEditingDelegateNotification:WebViewDidBeginEditingNotification selector:@selector(webViewDidBeginEditing:)]; + [self registerForEditingDelegateNotification:WebViewDidChangeNotification selector:@selector(webViewDidChange:)]; + [self registerForEditingDelegateNotification:WebViewDidEndEditingNotification selector:@selector(webViewDidEndEditing:)]; + [self registerForEditingDelegateNotification:WebViewDidChangeTypingStyleNotification selector:@selector(webViewDidChangeTypingStyle:)]; + [self registerForEditingDelegateNotification:WebViewDidChangeSelectionNotification selector:@selector(webViewDidChangeSelection:)]; +} + +- (id)editingDelegate +{ + return _private->editingDelegate; +} + +- (DOMCSSStyleDeclaration *)styleDeclarationWithText:(NSString *)text +{ + // FIXME: Should this really be attached to the document with the current selection? + DOMCSSStyleDeclaration *decl = [[[self _selectedOrMainFrame] DOMDocument] createCSSStyleDeclaration]; + [decl setCssText:text]; + return decl; +} + +@end + +@implementation WebView (WebViewGrammarChecking) + +// FIXME: This method should be merged into WebViewEditing when we're not in API freeze +- (BOOL)isGrammarCheckingEnabled +{ +#ifdef BUILDING_ON_TIGER + return NO; +#else + return grammarCheckingEnabled; +#endif +} + +#ifndef BUILDING_ON_TIGER +// FIXME: This method should be merged into WebViewEditing when we're not in API freeze +- (void)setGrammarCheckingEnabled:(BOOL)flag +{ + if (grammarCheckingEnabled == flag) + return; + + grammarCheckingEnabled = flag; + [[NSUserDefaults standardUserDefaults] setBool:grammarCheckingEnabled forKey:WebGrammarCheckingEnabled]; + + // FIXME 4811447: workaround for lack of API + NSSpellChecker *spellChecker = [NSSpellChecker sharedSpellChecker]; + if ([spellChecker respondsToSelector:@selector(_updateGrammar)]) + [spellChecker performSelector:@selector(_updateGrammar)]; + + // We call _preflightSpellChecker when turning continuous spell checking on, but we don't need to do that here + // because grammar checking only occurs on code paths that already preflight spell checking appropriately. + + if (![self isGrammarCheckingEnabled]) + [[self mainFrame] _unmarkAllBadGrammar]; +} + +// FIXME: This method should be merged into WebIBActions when we're not in API freeze +- (void)toggleGrammarChecking:(id)sender +{ + [self setGrammarCheckingEnabled:![self isGrammarCheckingEnabled]]; +} +#endif + +@end + +@implementation WebView (WebViewUndoableEditing) + +- (void)replaceSelectionWithNode:(DOMNode *)node +{ + [[self _bridgeForSelectedOrMainFrame] replaceSelectionWithNode:node selectReplacement:YES smartReplace:NO matchStyle:NO]; +} + +- (void)replaceSelectionWithText:(NSString *)text +{ + [[self _bridgeForSelectedOrMainFrame] replaceSelectionWithText:text selectReplacement:YES smartReplace:NO]; +} + +- (void)replaceSelectionWithMarkupString:(NSString *)markupString +{ + [[self _bridgeForSelectedOrMainFrame] replaceSelectionWithMarkupString:markupString baseURLString:nil selectReplacement:YES smartReplace:NO]; +} + +- (void)replaceSelectionWithArchive:(WebArchive *)archive +{ + [[[[self _bridgeForSelectedOrMainFrame] webFrame] _dataSource] _replaceSelectionWithArchive:archive selectReplacement:YES]; +} + +- (void)deleteSelection +{ + WebFrame *webFrame = [self _selectedOrMainFrame]; + Frame* coreFrame = core(webFrame); + if (coreFrame) + coreFrame->editor()->deleteSelectionWithSmartDelete([(WebHTMLView *)[[webFrame frameView] documentView] _canSmartCopyOrDelete]); +} + +- (void)applyStyle:(DOMCSSStyleDeclaration *)style +{ + // We don't know enough at thls level to pass in a relevant WebUndoAction; we'd have to + // change the API to allow this. + WebFrame *webFrame = [self _selectedOrMainFrame]; + Frame* coreFrame = core(webFrame); + if (coreFrame) + coreFrame->editor()->applyStyle(core(style)); +} + +@end + +@implementation WebView (WebViewEditingActions) + +- (void)_performResponderOperation:(SEL)selector with:(id)parameter +{ + static BOOL reentered = NO; + if (reentered) { + [[self nextResponder] tryToPerform:selector with:parameter]; + return; + } + + // There are two possibilities here. + // + // One is that WebView has been called in its role as part of the responder chain. + // In that case, it's fine to call the first responder and end up calling down the + // responder chain again. Later we will return here with reentered = YES and continue + // past the WebView. + // + // The other is that we are being called directly, in which case we want to pass the + // selector down to the view inside us that can handle it, and continue down the + // responder chain as usual. + + // Pass this selector down to the first responder. + NSResponder *responder = [self _responderForResponderOperations]; + reentered = YES; + [responder tryToPerform:selector with:parameter]; + reentered = NO; +} + +#define FORWARD(name) \ + - (void)name:(id)sender { [self _performResponderOperation:_cmd with:sender]; } + +FOR_EACH_RESPONDER_SELECTOR(FORWARD) + +- (void)insertText:(NSString *)text +{ + [self _performResponderOperation:_cmd with:text]; +} + +@end + +@implementation WebView (WebViewEditingInMail) + +- (void)_insertNewlineInQuotedContent; +{ + [[self _bridgeForSelectedOrMainFrame] insertParagraphSeparatorInQuotedContent]; +} + +- (void)_replaceSelectionWithNode:(DOMNode *)node matchStyle:(BOOL)matchStyle +{ + [[self _bridgeForSelectedOrMainFrame] replaceSelectionWithNode:node selectReplacement:YES smartReplace:NO matchStyle:matchStyle]; +} + +@end + +static WebFrameView *containingFrameView(NSView *view) +{ + while (view && ![view isKindOfClass:[WebFrameView class]]) + view = [view superview]; + return (WebFrameView *)view; +} + +@implementation WebView (WebFileInternal) + ++ (void)_setCacheModel:(WebCacheModel)cacheModel +{ + if (s_didSetCacheModel && cacheModel == s_cacheModel) + return; + + NSString *nsurlCacheDirectory = [(NSString *)WKCopyFoundationCacheDirectory() autorelease]; + if (!nsurlCacheDirectory) + nsurlCacheDirectory = NSHomeDirectory(); + + // As a fudge factor, use 1000 instead of 1024, in case the reported byte + // count doesn't align exactly to a megabyte boundary. + vm_size_t memSize = WebMemorySize() / 1024 / 1000; + unsigned long long diskFreeSize = WebVolumeFreeSize(nsurlCacheDirectory) / 1024 / 1000; + NSURLCache *nsurlCache = [NSURLCache sharedURLCache]; + + unsigned cacheTotalCapacity = 0; + unsigned cacheMinDeadCapacity = 0; + unsigned cacheMaxDeadCapacity = 0; + + unsigned pageCacheCapacity = 0; + + NSUInteger nsurlCacheMemoryCapacity = 0; + NSUInteger nsurlCacheDiskCapacity = 0; + + switch (cacheModel) { + case WebCacheModelDocumentViewer: { + // Page cache capacity (in pages) + pageCacheCapacity = 0; + + // Object cache capacities (in bytes) + if (memSize >= 4096) + cacheTotalCapacity = 256 * 1024 * 1024; + else if (memSize >= 3072) + cacheTotalCapacity = 192 * 1024 * 1024; + else if (memSize >= 2048) + cacheTotalCapacity = 128 * 1024 * 1024; + else if (memSize >= 1536) + cacheTotalCapacity = 86 * 1024 * 1024; + else if (memSize >= 1024) + cacheTotalCapacity = 64 * 1024 * 1024; + else if (memSize >= 512) + cacheTotalCapacity = 32 * 1024 * 1024; + else if (memSize >= 256) + cacheTotalCapacity = 16 * 1024 * 1024; + + cacheMinDeadCapacity = 0; + cacheMaxDeadCapacity = 0; + + // Foundation memory cache capacity (in bytes) + nsurlCacheMemoryCapacity = 0; + + // Foundation disk cache capacity (in bytes) + nsurlCacheDiskCapacity = [nsurlCache diskCapacity]; + + break; + } + case WebCacheModelDocumentBrowser: { + // Page cache capacity (in pages) + if (memSize >= 1024) + pageCacheCapacity = 3; + else if (memSize >= 512) + pageCacheCapacity = 2; + else if (memSize >= 256) + pageCacheCapacity = 1; + else + pageCacheCapacity = 0; + + // Object cache capacities (in bytes) + if (memSize >= 4096) + cacheTotalCapacity = 256 * 1024 * 1024; + else if (memSize >= 3072) + cacheTotalCapacity = 192 * 1024 * 1024; + else if (memSize >= 2048) + cacheTotalCapacity = 128 * 1024 * 1024; + else if (memSize >= 1536) + cacheTotalCapacity = 86 * 1024 * 1024; + else if (memSize >= 1024) + cacheTotalCapacity = 64 * 1024 * 1024; + else if (memSize >= 512) + cacheTotalCapacity = 32 * 1024 * 1024; + else if (memSize >= 256) + cacheTotalCapacity = 16 * 1024 * 1024; + + cacheMinDeadCapacity = cacheTotalCapacity / 8; + cacheMaxDeadCapacity = cacheTotalCapacity / 4; + + // Foundation memory cache capacity (in bytes) + if (memSize >= 2048) + nsurlCacheMemoryCapacity = 4 * 1024 * 1024; + else if (memSize >= 1024) + nsurlCacheMemoryCapacity = 2 * 1024 * 1024; + else if (memSize >= 512) + nsurlCacheMemoryCapacity = 1 * 1024 * 1024; + else + nsurlCacheMemoryCapacity = 512 * 1024; + + // Foundation disk cache capacity (in bytes) + if (diskFreeSize >= 16384) + nsurlCacheDiskCapacity = 50 * 1024 * 1024; + else if (diskFreeSize >= 8192) + nsurlCacheDiskCapacity = 40 * 1024 * 1024; + else if (diskFreeSize >= 4096) + nsurlCacheDiskCapacity = 30 * 1024 * 1024; + else + nsurlCacheDiskCapacity = 20 * 1024 * 1024; + + break; + } + case WebCacheModelPrimaryWebBrowser: { + // Page cache capacity (in pages) + // (Research indicates that value / page drops substantially after 3 pages.) + if (memSize >= 8192) + pageCacheCapacity = 7; + if (memSize >= 4096) + pageCacheCapacity = 6; + else if (memSize >= 2048) + pageCacheCapacity = 5; + else if (memSize >= 1024) + pageCacheCapacity = 4; + else if (memSize >= 512) + pageCacheCapacity = 3; + else if (memSize >= 256) + pageCacheCapacity = 2; + else + pageCacheCapacity = 1; + + // Object cache capacities (in bytes) + // (Testing indicates that value / MB depends heavily on content and + // browsing pattern. Even growth above 128MB can have substantial + // value / MB for some content / browsing patterns.) + if (memSize >= 4096) + cacheTotalCapacity = 512 * 1024 * 1024; + else if (memSize >= 3072) + cacheTotalCapacity = 384 * 1024 * 1024; + else if (memSize >= 2048) + cacheTotalCapacity = 256 * 1024 * 1024; + else if (memSize >= 1536) + cacheTotalCapacity = 172 * 1024 * 1024; + else if (memSize >= 1024) + cacheTotalCapacity = 128 * 1024 * 1024; + else if (memSize >= 512) + cacheTotalCapacity = 64 * 1024 * 1024; + else if (memSize >= 256) + cacheTotalCapacity = 32 * 1024 * 1024; + + cacheMinDeadCapacity = cacheTotalCapacity / 4; + cacheMaxDeadCapacity = cacheTotalCapacity / 2; + + // This code is here to avoid a PLT regression. We can remove it if we + // can prove that the overall system gain would justify the regression. + cacheMaxDeadCapacity = max(24u, cacheMaxDeadCapacity); + + // Foundation memory cache capacity (in bytes) + // (These values are small because WebCore does most caching itself.) + if (memSize >= 1024) + nsurlCacheMemoryCapacity = 4 * 1024 * 1024; + else if (memSize >= 512) + nsurlCacheMemoryCapacity = 2 * 1024 * 1024; + else if (memSize >= 256) + nsurlCacheMemoryCapacity = 1 * 1024 * 1024; + else + nsurlCacheMemoryCapacity = 512 * 1024; + + // Foundation disk cache capacity (in bytes) + if (diskFreeSize >= 16384) + nsurlCacheDiskCapacity = 175 * 1024 * 1024; + else if (diskFreeSize >= 8192) + nsurlCacheDiskCapacity = 150 * 1024 * 1024; + else if (diskFreeSize >= 4096) + nsurlCacheDiskCapacity = 125 * 1024 * 1024; + else if (diskFreeSize >= 2048) + nsurlCacheDiskCapacity = 100 * 1024 * 1024; + else if (diskFreeSize >= 1024) + nsurlCacheDiskCapacity = 75 * 1024 * 1024; + else + nsurlCacheDiskCapacity = 50 * 1024 * 1024; + + break; + } + default: + ASSERT_NOT_REACHED(); + }; + +#ifdef BUILDING_ON_TIGER + // Don't use a big Foundation disk cache on Tiger because, according to the + // PLT, the Foundation disk cache on Tiger is slower than the network. + nsurlCacheDiskCapacity = [[NSURLCache sharedURLCache] diskCapacity]; +#else + // Don't use a big Foundation disk cache on older versions of Leopard because + // doing so causes a SPOD on launch (). + if (NSVersionOfRunTimeLibrary("CFNetwork") < WEBKIT_FIRST_CFNETWORK_VERSION_WITH_LARGE_DISK_CACHE_FIX) + nsurlCacheDiskCapacity = [[NSURLCache sharedURLCache] diskCapacity]; +#endif + + // Don't shrink a big disk cache, since that would cause churn. + nsurlCacheDiskCapacity = max(nsurlCacheDiskCapacity, [nsurlCache diskCapacity]); + + cache()->setCapacities(cacheMinDeadCapacity, cacheMaxDeadCapacity, cacheTotalCapacity); + pageCache()->setCapacity(pageCacheCapacity); + [nsurlCache setMemoryCapacity:nsurlCacheMemoryCapacity]; + [nsurlCache setDiskCapacity:nsurlCacheDiskCapacity]; + + s_cacheModel = cacheModel; + s_didSetCacheModel = YES; +} + ++ (WebCacheModel)_cacheModel +{ + return s_cacheModel; +} + ++ (WebCacheModel)_didSetCacheModel +{ + return s_didSetCacheModel; +} + ++ (WebCacheModel)_maxCacheModelInAnyInstance +{ + WebCacheModel cacheModel = WebCacheModelDocumentViewer; + NSEnumerator *enumerator = [(NSMutableSet *)allWebViewsSet objectEnumerator]; + while (WebPreferences *preferences = [[enumerator nextObject] preferences]) + cacheModel = max(cacheModel, [preferences cacheModel]); + return cacheModel; +} + ++ (void)_preferencesChangedNotification:(NSNotification *)notification +{ + WebPreferences *preferences = (WebPreferences *)[notification object]; + ASSERT([preferences isKindOfClass:[WebPreferences class]]); + + WebCacheModel cacheModel = [preferences cacheModel]; + if (![self _didSetCacheModel] || cacheModel > [self _cacheModel]) + [self _setCacheModel:cacheModel]; + else if (cacheModel < [self _cacheModel]) + [self _setCacheModel:max([[WebPreferences standardPreferences] cacheModel], [self _maxCacheModelInAnyInstance])]; +} + ++ (void)_preferencesRemovedNotification:(NSNotification *)notification +{ + WebPreferences *preferences = (WebPreferences *)[notification object]; + ASSERT([preferences isKindOfClass:[WebPreferences class]]); + + if ([preferences cacheModel] == [self _cacheModel]) + [self _setCacheModel:max([[WebPreferences standardPreferences] cacheModel], [self _maxCacheModelInAnyInstance])]; +} + +- (WebFrame *)_focusedFrame +{ + NSResponder *resp = [[self window] firstResponder]; + if (resp && [resp isKindOfClass:[NSView class]] && [(NSView *)resp isDescendantOf:[[self mainFrame] frameView]]) { + WebFrameView *frameView = containingFrameView((NSView *)resp); + ASSERT(frameView != nil); + return [frameView webFrame]; + } + + return nil; +} + +- (WebFrame *)_selectedOrMainFrame +{ + WebFrame *result = [self selectedFrame]; + if (result == nil) + result = [self mainFrame]; + return result; +} + +- (WebFrameBridge *)_bridgeForSelectedOrMainFrame +{ + return [[self _selectedOrMainFrame] _bridge]; +} + +- (BOOL)_isLoading +{ + WebFrame *mainFrame = [self mainFrame]; + return [[mainFrame _dataSource] isLoading] + || [[mainFrame provisionalDataSource] isLoading]; +} + +- (WebFrameView *)_frameViewAtWindowPoint:(NSPoint)point +{ + if (_private->closed) + return nil; + NSView *view = [self hitTest:[[self superview] convertPoint:point fromView:nil]]; + if (![view isDescendantOf:[[self mainFrame] frameView]]) + return nil; + WebFrameView *frameView = containingFrameView(view); + ASSERT(frameView); + return frameView; +} + ++ (void)_preflightSpellCheckerNow:(id)sender +{ + [[NSSpellChecker sharedSpellChecker] _preflightChosenSpellServer]; +} + ++ (void)_preflightSpellChecker +{ + // As AppKit does, we wish to delay tickling the shared spellchecker into existence on application launch. + if ([NSSpellChecker sharedSpellCheckerExists]) { + [self _preflightSpellCheckerNow:self]; + } else { + [self performSelector:@selector(_preflightSpellCheckerNow:) withObject:self afterDelay:2.0]; + } +} + +- (BOOL)_continuousCheckingAllowed +{ + static BOOL allowContinuousSpellChecking = YES; + static BOOL readAllowContinuousSpellCheckingDefault = NO; + if (!readAllowContinuousSpellCheckingDefault) { + if ([[NSUserDefaults standardUserDefaults] objectForKey:@"NSAllowContinuousSpellChecking"]) { + allowContinuousSpellChecking = [[NSUserDefaults standardUserDefaults] boolForKey:@"NSAllowContinuousSpellChecking"]; + } + readAllowContinuousSpellCheckingDefault = YES; + } + return allowContinuousSpellChecking; +} + +- (NSResponder *)_responderForResponderOperations +{ + NSResponder *responder = [[self window] firstResponder]; + WebFrameView *mainFrameView = [[self mainFrame] frameView]; + + // If the current responder is outside of the webview, use our main frameView or its + // document view. We also do this for subviews of self that are siblings of the main + // frameView since clients might insert non-webview-related views there (see 4552713). + if (responder != self && ![mainFrameView _web_firstResponderIsSelfOrDescendantView]) { + responder = [mainFrameView documentView]; + if (!responder) + responder = mainFrameView; + } + return responder; +} + +- (void)_openFrameInNewWindowFromMenu:(NSMenuItem *)sender +{ + ASSERT_ARG(sender, [sender isKindOfClass:[NSMenuItem class]]); + + NSDictionary *element = [sender representedObject]; + ASSERT([element isKindOfClass:[NSDictionary class]]); + + NSURLRequest *request = [[[[element objectForKey:WebElementFrameKey] dataSource] request] copy]; + ASSERT(request); + + [self _openNewWindowWithRequest:request]; + [request release]; +} + +- (void)_searchWithGoogleFromMenu:(id)sender +{ + id documentView = [[[self selectedFrame] frameView] documentView]; + if (![documentView conformsToProtocol:@protocol(WebDocumentText)]) { + return; + } + + NSString *selectedString = [(id )documentView selectedString]; + if ([selectedString length] == 0) { + return; + } + + NSPasteboard *pasteboard = [NSPasteboard pasteboardWithUniqueName]; + [pasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; + NSMutableString *s = [selectedString mutableCopy]; + const unichar nonBreakingSpaceCharacter = 0xA0; + NSString *nonBreakingSpaceString = [NSString stringWithCharacters:&nonBreakingSpaceCharacter length:1]; + [s replaceOccurrencesOfString:nonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])]; + [pasteboard setString:s forType:NSStringPboardType]; + [s release]; + + // FIXME: seems fragile to use the service by name, but this is what AppKit does + NSPerformService(@"Search With Google", pasteboard); +} + +- (void)_searchWithSpotlightFromMenu:(id)sender +{ + id documentView = [[[self selectedFrame] frameView] documentView]; + if (![documentView conformsToProtocol:@protocol(WebDocumentText)]) { + return; + } + + NSString *selectedString = [(id )documentView selectedString]; + if ([selectedString length] == 0) { + return; + } + + (void)HISearchWindowShow((CFStringRef)selectedString, kNilOptions); +} + +// Slightly funky method that lets us have one copy of the logic for finding docViews that can do +// text sizing. It returns whether it found any "suitable" doc views. It sends sel to any suitable +// doc views, or if sel==0 we do nothing to them. For doc views that track our size factor, they are +// suitable if doTrackingViews==YES (which in practice means that our size factor isn't at its max or +// min). For doc views that don't track it, we send them testSel to determine suitablility. If we +// do find any suitable tracking doc views and newScaleFactor!=0, we will set the common scale factor +// to that new factor before we send sel to any of them. +- (BOOL)_performTextSizingSelector:(SEL)sel withObject:(id)arg onTrackingDocs:(BOOL)doTrackingViews selForNonTrackingDocs:(SEL)testSel newScaleFactor:(float)newScaleFactor +{ + if ([[self mainFrame] _dataSource] == nil) + return NO; + + BOOL foundSome = NO; + NSArray *docViews = [[self mainFrame] _documentViews]; + for (int i = [docViews count]-1; i >= 0; i--) { + id docView = [docViews objectAtIndex:i]; + if ([docView conformsToProtocol:@protocol(_WebDocumentTextSizing)]) { + id <_WebDocumentTextSizing> sizingDocView = (id <_WebDocumentTextSizing>)docView; + BOOL isSuitable; + if ([sizingDocView _tracksCommonSizeFactor]) { + isSuitable = doTrackingViews; + if (isSuitable && newScaleFactor != 0) + _private->textSizeMultiplier = newScaleFactor; + } else { + // Incantation to perform a selector returning a BOOL. + isSuitable = ((BOOL(*)(id, SEL))objc_msgSend)(sizingDocView, testSel); + } + + if (isSuitable) { + if (sel != 0) { + foundSome = YES; + [sizingDocView performSelector:sel withObject:arg]; + } else { + // if we're just called for the benefit of the return value, we can return at first match + return YES; + } + } + } + } + + return foundSome; +} + +- (void)_notifyTextSizeMultiplierChanged +{ + if ([[self mainFrame] _dataSource] == nil) + return; + + NSArray *docViews = [[self mainFrame] _documentViews]; + for (int i = [docViews count]-1; i >= 0; i--) { + id docView = [docViews objectAtIndex:i]; + if ([docView conformsToProtocol:@protocol(_WebDocumentTextSizing)] == NO) + continue; + + id <_WebDocumentTextSizing> sizingDocView = (id <_WebDocumentTextSizing>)docView; + if ([sizingDocView _tracksCommonSizeFactor]) + [sizingDocView _textSizeMultiplierChanged]; + } + +} + +@end + +@implementation WebView (WebViewInternal) + +- (BOOL)_becomingFirstResponderFromOutside +{ + return _private->becomingFirstResponderFromOutside; +} + +- (void)_receivedIconChangedNotification:(NSNotification *)notification +{ + // Get the URL for this notification + NSDictionary *userInfo = [notification userInfo]; + ASSERT([userInfo isKindOfClass:[NSDictionary class]]); + NSString *urlString = [userInfo objectForKey:WebIconNotificationUserInfoURLKey]; + ASSERT([urlString isKindOfClass:[NSString class]]); + + // If that URL matches the current main frame, dispatch the delegate call, which will also unregister + // us for this notification + if ([[self mainFrameURL] isEqualTo:urlString]) + [self _dispatchDidReceiveIconFromWebFrame:[self mainFrame]]; +} + +- (void)_registerForIconNotification:(BOOL)listen +{ + if (listen) + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_receivedIconChangedNotification:) name:WebIconDatabaseDidAddIconNotification object:nil]; + else + [[NSNotificationCenter defaultCenter] removeObserver:self name:WebIconDatabaseDidAddIconNotification object:nil]; +} + +- (void)_dispatchDidReceiveIconFromWebFrame:(WebFrame *)webFrame +{ + // FIXME: This willChangeValueForKey call is too late, because the icon has already changed by now. + [self _willChangeValueForKey:_WebMainFrameIconKey]; + + // Since we definitely have an icon and are about to send out the delegate call for that, this WebView doesn't need to listen for the general + // notification any longer + [self _registerForIconNotification:NO]; + + WebFrameLoadDelegateImplementationCache implementations = WebViewGetFrameLoadDelegateImplementations(self); + if (implementations.didReceiveIconForFrameFunc) { + Image* image = iconDatabase()->iconForPageURL(core(webFrame)->loader()->url().url(), IntSize(16, 16)); + if (NSImage *icon = webGetNSImage(image, NSMakeSize(16, 16))) + CallFrameLoadDelegate(implementations.didReceiveIconForFrameFunc, self, @selector(webView:didReceiveIcon:forFrame:), icon, webFrame); + } + + [self _didChangeValueForKey:_WebMainFrameIconKey]; +} + +- (NSString *)_userVisibleBundleVersionFromFullVersion:(NSString *)fullVersion +{ + // If the version is 4 digits long or longer, then the first digit represents + // the version of the OS. Our user agent string should not include this first digit, + // so strip it off and report the rest as the version. + NSRange nonDigitRange = [fullVersion rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]]; + if (nonDigitRange.location == NSNotFound && [fullVersion length] >= 4) + return [fullVersion substringFromIndex:1]; + if (nonDigitRange.location != NSNotFound && nonDigitRange.location >= 4) + return [fullVersion substringFromIndex:1]; + return fullVersion; +} + +- (NSString *)_userAgentWithApplicationName:(NSString *)applicationName andWebKitVersion:(NSString *)version +{ + NSString *language = [NSUserDefaults _webkit_preferredLanguageCode]; + if ([applicationName length]) + return [NSString stringWithFormat:@"Mozilla/5.0 (Macintosh; U; " PROCESSOR " Mac OS X; %@) AppleWebKit/%@ (KHTML, like Gecko) %@", language, version, applicationName]; + return [NSString stringWithFormat:@"Mozilla/5.0 (Macintosh; U; " PROCESSOR " Mac OS X; %@) AppleWebKit/%@ (KHTML, like Gecko)", language, version]; +} + +// Get the appropriate user-agent string for a particular URL. +- (WebCore::String)_userAgentForURL:(const WebCore::KURL&)url +{ + if (_private->useSiteSpecificSpoofing) { + // FIXME: Make this a hash table lookup if more domains need spoofing. + // FIXME: Remove yahoo.com once is fixed. + if (url.host().endsWith("yahoo.com")) { + static String yahooUserAgent([self _userAgentWithApplicationName:_private->applicationNameForUserAgent andWebKitVersion:@"422"]); + return yahooUserAgent; + } + + // FIXME: Remove flickr.com workaround once is fixed + if (url.host().endsWith("flickr.com")) { + // Safari 2.0.4's user agent string works here + static String safari204UserAgent([self _userAgentWithApplicationName:@"Safari/419.3" andWebKitVersion:@"419"]); + return safari204UserAgent; + } + } + + if (_private->userAgent->isNull()) { + NSString *sourceVersion = [[NSBundle bundleForClass:[WebView class]] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]; + sourceVersion = [self _userVisibleBundleVersionFromFullVersion:sourceVersion]; + *_private->userAgent = [self _userAgentWithApplicationName:_private->applicationNameForUserAgent andWebKitVersion:sourceVersion]; + } + + return *_private->userAgent; +} + +- (void)_addObject:(id)object forIdentifier:(unsigned long)identifier +{ + ASSERT(!_private->identifierMap->contains(identifier)); + + // If the identifier map is initially empty it means we're starting a load + // of something. The semantic is that the web view should be around as long + // as something is loading. Because of that we retain the web view. + if (_private->identifierMap->isEmpty()) + CFRetain(self); + + _private->identifierMap->set(identifier, object); +} + +- (id)_objectForIdentifier:(unsigned long)identifier +{ + return _private->identifierMap->get(identifier).get(); +} + +- (void)_removeObjectForIdentifier:(unsigned long)identifier +{ + HashMap >::iterator it = _private->identifierMap->find(identifier); + + // FIXME: This is currently needed because of a bug that causes didFail to be sent twice + // sometimes, see for more information. + if (it == _private->identifierMap->end()) + return; + + _private->identifierMap->remove(it); + + // If the identifier map is now empty it means we're no longer loading anything + // and we should release the web view. + if (_private->identifierMap->isEmpty()) + CFRelease(self); +} + +@end + +// We use these functions to call the delegates and block exceptions. These functions are +// declared inside a WebView category to get direct access to the delegate data memebers, +// preventing more ObjC message dispatch and compensating for the expense of the @try/@catch. + +@implementation WebView (WebCallDelegateFunctions) + +#if !(defined(__i386__) || defined(__x86_64__)) +typedef double (*ObjCMsgSendFPRet)(id, SEL, ...); +static const ObjCMsgSendFPRet objc_msgSend_fpret = reinterpret_cast(objc_msgSend); +#endif + +static inline id CallDelegate(WebView *self, id delegate, SEL selector) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return nil; + if (!self->_private->catchesDelegateExceptions) + return objc_msgSend(delegate, selector, self); + @try { + return objc_msgSend(delegate, selector, self); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(WebView *self, id delegate, SEL selector, id object) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return nil; + if (!self->_private->catchesDelegateExceptions) + return objc_msgSend(delegate, selector, self, object); + @try { + return objc_msgSend(delegate, selector, self, object); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(WebView *self, id delegate, SEL selector, NSRect rect) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return nil; + if (!self->_private->catchesDelegateExceptions) + return reinterpret_cast(objc_msgSend)(delegate, selector, self, rect); + @try { + return reinterpret_cast(objc_msgSend)(delegate, selector, self, rect); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(WebView *self, id delegate, SEL selector, id object1, id object2) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return nil; + if (!self->_private->catchesDelegateExceptions) + return objc_msgSend(delegate, selector, self, object1, object2); + @try { + return objc_msgSend(delegate, selector, self, object1, object2); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(WebView *self, id delegate, SEL selector, id object, BOOL boolean) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return nil; + if (!self->_private->catchesDelegateExceptions) + return objc_msgSend(delegate, selector, self, object, boolean); + @try { + return objc_msgSend(delegate, selector, self, object, boolean); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(WebView *self, id delegate, SEL selector, id object1, id object2, id object3) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return nil; + if (!self->_private->catchesDelegateExceptions) + return objc_msgSend(delegate, selector, self, object1, object2, object3); + @try { + return objc_msgSend(delegate, selector, self, object1, object2, object3); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(WebView *self, id delegate, SEL selector, id object, NSUInteger integer) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return nil; + if (!self->_private->catchesDelegateExceptions) + return objc_msgSend(delegate, selector, self, object, integer); + @try { + return objc_msgSend(delegate, selector, self, object, integer); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline float CallDelegateReturningFloat(WebView *self, id delegate, SEL selector) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return 0.0f; + if (!self->_private->catchesDelegateExceptions) + return static_cast(objc_msgSend_fpret(delegate, selector, self)); + @try { + return static_cast(objc_msgSend_fpret(delegate, selector, self)); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return 0.0f; +} + +static inline BOOL CallDelegateReturningBoolean(BOOL result, WebView *self, id delegate, SEL selector) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return result; + if (!self->_private->catchesDelegateExceptions) + return reinterpret_cast(objc_msgSend)(delegate, selector, self); + @try { + return reinterpret_cast(objc_msgSend)(delegate, selector, self); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return result; +} + +static inline BOOL CallDelegateReturningBoolean(BOOL result, WebView *self, id delegate, SEL selector, id object) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return result; + if (!self->_private->catchesDelegateExceptions) + return reinterpret_cast(objc_msgSend)(delegate, selector, self, object); + @try { + return reinterpret_cast(objc_msgSend)(delegate, selector, self, object); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return result; +} + +static inline BOOL CallDelegateReturningBoolean(BOOL result, WebView *self, id delegate, SEL selector, id object, BOOL boolean) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return result; + if (!self->_private->catchesDelegateExceptions) + return reinterpret_cast(objc_msgSend)(delegate, selector, self, object, boolean); + @try { + return reinterpret_cast(objc_msgSend)(delegate, selector, self, object, boolean); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return result; +} + +static inline BOOL CallDelegateReturningBoolean(BOOL result, WebView *self, id delegate, SEL selector, id object1, id object2) +{ + if (!delegate || ![delegate respondsToSelector:selector]) + return result; + if (!self->_private->catchesDelegateExceptions) + return reinterpret_cast(objc_msgSend)(delegate, selector, self, object1, object2); + @try { + return reinterpret_cast(objc_msgSend)(delegate, selector, self, object1, object2); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return result; +} + +static inline id CallDelegate(IMP implementation, WebView *self, id delegate, SEL selector) +{ + if (!delegate) + return nil; + if (!self->_private->catchesDelegateExceptions) + return implementation(delegate, selector, self); + @try { + return implementation(delegate, selector, self); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(IMP implementation, WebView *self, id delegate, SEL selector, id object) +{ + if (!delegate) + return nil; + if (!self->_private->catchesDelegateExceptions) + return implementation(delegate, selector, self, object); + @try { + return implementation(delegate, selector, self, object); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(IMP implementation, WebView *self, id delegate, SEL selector, id object1, id object2) +{ + if (!delegate) + return nil; + if (!self->_private->catchesDelegateExceptions) + return implementation(delegate, selector, self, object1, object2); + @try { + return implementation(delegate, selector, self, object1, object2); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(IMP implementation, WebView *self, id delegate, SEL selector, id object1, id object2, id object3) +{ + if (!delegate) + return nil; + if (!self->_private->catchesDelegateExceptions) + return implementation(delegate, selector, self, object1, object2, object3); + @try { + return implementation(delegate, selector, self, object1, object2, object3); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(IMP implementation, WebView *self, id delegate, SEL selector, id object1, id object2, id object3, id object4) +{ + if (!delegate) + return nil; + if (!self->_private->catchesDelegateExceptions) + return implementation(delegate, selector, self, object1, object2, object3, object4); + @try { + return implementation(delegate, selector, self, object1, object2, object3, object4); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(IMP implementation, WebView *self, id delegate, SEL selector, id object1, NSInteger integer, id object2) +{ + if (!delegate) + return nil; + if (!self->_private->catchesDelegateExceptions) + return implementation(delegate, selector, self, object1, integer, object2); + @try { + return implementation(delegate, selector, self, object1, integer, object2); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(IMP implementation, WebView *self, id delegate, SEL selector, id object1, id object2, NSInteger integer, id object3) +{ + if (!delegate) + return nil; + if (!self->_private->catchesDelegateExceptions) + return implementation(delegate, selector, self, object1, object2, integer, object3); + @try { + return implementation(delegate, selector, self, object1, object2, integer, object3); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +static inline id CallDelegate(IMP implementation, WebView *self, id delegate, SEL selector, id object1, NSTimeInterval interval, id object2, id object3) +{ + if (!delegate) + return nil; + if (!self->_private->catchesDelegateExceptions) + return implementation(delegate, selector, self, object1, interval, object2, object3); + @try { + return implementation(delegate, selector, self, object1, interval, object2, object3); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +id CallUIDelegate(WebView *self, SEL selector) +{ + return CallDelegate(self, self->_private->UIDelegate, selector); +} + +id CallUIDelegate(WebView *self, SEL selector, id object) +{ + return CallDelegate(self, self->_private->UIDelegate, selector, object); +} + +id CallUIDelegate(WebView *self, SEL selector, id object, BOOL boolean) +{ + return CallDelegate(self, self->_private->UIDelegate, selector, object, boolean); +} + +id CallUIDelegate(WebView *self, SEL selector, NSRect rect) +{ + return CallDelegate(self, self->_private->UIDelegate, selector, rect); +} + +id CallUIDelegate(WebView *self, SEL selector, id object1, id object2) +{ + return CallDelegate(self, self->_private->UIDelegate, selector, object1, object2); +} + +id CallUIDelegate(WebView *self, SEL selector, id object1, id object2, id object3) +{ + return CallDelegate(self, self->_private->UIDelegate, selector, object1, object2, object3); +} + +id CallUIDelegate(WebView *self, SEL selector, id object, NSUInteger integer) +{ + return CallDelegate(self, self->_private->UIDelegate, selector, object, integer); +} + +float CallUIDelegateReturningFloat(WebView *self, SEL selector) +{ + return CallDelegateReturningFloat(self, self->_private->UIDelegate, selector); +} + +BOOL CallUIDelegateReturningBoolean(BOOL result, WebView *self, SEL selector) +{ + return CallDelegateReturningBoolean(result, self, self->_private->UIDelegate, selector); +} + +BOOL CallUIDelegateReturningBoolean(BOOL result, WebView *self, SEL selector, id object) +{ + return CallDelegateReturningBoolean(result, self, self->_private->UIDelegate, selector, object); +} + +BOOL CallUIDelegateReturningBoolean(BOOL result, WebView *self, SEL selector, id object, BOOL boolean) +{ + return CallDelegateReturningBoolean(result, self, self->_private->UIDelegate, selector, object, boolean); +} + +BOOL CallUIDelegateReturningBoolean(BOOL result, WebView *self, SEL selector, id object1, id object2) +{ + return CallDelegateReturningBoolean(result, self, self->_private->UIDelegate, selector, object1, object2); +} + +id CallFrameLoadDelegate(IMP implementation, WebView *self, SEL selector) +{ + return CallDelegate(implementation, self, self->_private->frameLoadDelegate, selector); +} + +id CallFrameLoadDelegate(IMP implementation, WebView *self, SEL selector, id object) +{ + return CallDelegate(implementation, self, self->_private->frameLoadDelegate, selector, object); +} + +id CallFrameLoadDelegate(IMP implementation, WebView *self, SEL selector, id object1, id object2) +{ + return CallDelegate(implementation, self, self->_private->frameLoadDelegate, selector, object1, object2); +} + +id CallFrameLoadDelegate(IMP implementation, WebView *self, SEL selector, id object1, id object2, id object3) +{ + return CallDelegate(implementation, self, self->_private->frameLoadDelegate, selector, object1, object2, object3); +} + +id CallFrameLoadDelegate(IMP implementation, WebView *self, SEL selector, id object1, id object2, id object3, id object4) +{ + return CallDelegate(implementation, self, self->_private->frameLoadDelegate, selector, object1, object2, object3, object4); +} + +id CallFrameLoadDelegate(IMP implementation, WebView *self, SEL selector, id object1, NSTimeInterval interval, id object2, id object3) +{ + return CallDelegate(implementation, self, self->_private->frameLoadDelegate, selector, object1, interval, object2, object3); +} + +id CallResourceLoadDelegate(IMP implementation, WebView *self, SEL selector, id object1, id object2) +{ + return CallDelegate(implementation, self, self->_private->resourceProgressDelegate, selector, object1, object2); +} + +id CallResourceLoadDelegate(IMP implementation, WebView *self, SEL selector, id object1, id object2, id object3) +{ + return CallDelegate(implementation, self, self->_private->resourceProgressDelegate, selector, object1, object2, object3); +} + +id CallResourceLoadDelegate(IMP implementation, WebView *self, SEL selector, id object1, id object2, id object3, id object4) +{ + return CallDelegate(implementation, self, self->_private->resourceProgressDelegate, selector, object1, object2, object3, object4); +} + +id CallResourceLoadDelegate(IMP implementation, WebView *self, SEL selector, id object1, NSInteger integer, id object2) +{ + return CallDelegate(implementation, self, self->_private->resourceProgressDelegate, selector, object1, integer, object2); +} + +id CallResourceLoadDelegate(IMP implementation, WebView *self, SEL selector, id object1, id object2, NSInteger integer, id object3) +{ + return CallDelegate(implementation, self, self->_private->resourceProgressDelegate, selector, object1, object2, integer, object3); +} + +// The form delegate needs to have it's own implementation, because the first argument is never the WebView + +id CallFormDelegate(WebView *self, SEL selector, id object1, id object2) +{ + id delegate = self->_private->formDelegate; + if (!delegate || ![delegate respondsToSelector:selector]) + return nil; + if (!self->_private->catchesDelegateExceptions) + return objc_msgSend(delegate, selector, object1, object2); + @try { + return objc_msgSend(delegate, selector, object1, object2); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +id CallFormDelegate(WebView *self, SEL selector, id object1, id object2, id object3, id object4, id object5) +{ + id delegate = self->_private->formDelegate; + if (!delegate || ![delegate respondsToSelector:selector]) + return nil; + if (!self->_private->catchesDelegateExceptions) + return objc_msgSend(delegate, selector, object1, object2, object3, object4, object5); + @try { + return objc_msgSend(delegate, selector, object1, object2, object3, object4, object5); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return nil; +} + +BOOL CallFormDelegateReturningBoolean(BOOL result, WebView *self, SEL selector, id object1, SEL selectorArg, id object2) +{ + id delegate = self->_private->formDelegate; + if (!delegate || ![delegate respondsToSelector:selector]) + return result; + if (!self->_private->catchesDelegateExceptions) + return reinterpret_cast(objc_msgSend)(delegate, selector, object1, selectorArg, object2); + @try { + return reinterpret_cast(objc_msgSend)(delegate, selector, object1, selectorArg, object2); + } @catch(id exception) { + ReportDiscardedDelegateException(selector, exception); + } + return result; +} + +@end