blob: e1669d7a89246a59a67e0bc3b25ad8b4acc9d244 [file] [log] [blame]
/*
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
* (C) 2006, 2007 Graham Dennis (graham.dennis@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 "WebHTMLView.h"
#import "DOMCSSStyleDeclarationInternal.h"
#import "DOMDocumentFragmentInternal.h"
#import "DOMDocumentInternal.h"
#import "DOMNodeInternal.h"
#import "DOMRangeInternal.h"
#import "WebArchive.h"
#import "WebClipView.h"
#import "WebDOMOperationsInternal.h"
#import "WebDataSourceInternal.h"
#import "WebDefaultUIDelegate.h"
#import "WebDelegateImplementationCaching.h"
#import "WebDocumentInternal.h"
#import "WebDynamicScrollBarsViewInternal.h"
#import "WebEditingDelegate.h"
#import "WebElementDictionary.h"
#import "WebFrameInternal.h"
#import "WebFramePrivate.h"
#import "WebFrameViewInternal.h"
#import "WebHTMLRepresentationPrivate.h"
#import "WebHTMLViewInternal.h"
#import "WebKitLogging.h"
#import "WebKitNSStringExtras.h"
#import "WebKitVersionChecks.h"
#import "WebLocalizableStringsInternal.h"
#import "WebNSEventExtras.h"
#import "WebNSFileManagerExtras.h"
#import "WebNSImageExtras.h"
#import "WebNSObjectExtras.h"
#import "WebNSPasteboardExtras.h"
#import "WebNSPrintOperationExtras.h"
#import "WebNSURLExtras.h"
#import "WebNSViewExtras.h"
#import "WebNetscapePluginView.h"
#import "WebNodeHighlight.h"
#import "WebPluginController.h"
#import "WebPreferences.h"
#import "WebPreferencesPrivate.h"
#import "WebResourcePrivate.h"
#import "WebTextCompletionController.h"
#import "WebTypesInternal.h"
#import "WebUIDelegatePrivate.h"
#import "WebViewInternal.h"
#import <AppKit/NSAccessibility.h>
#import <ApplicationServices/ApplicationServices.h>
#import <WebCore/CSSStyleDeclaration.h>
#import <WebCore/CachedImage.h>
#import <WebCore/CachedResourceClient.h>
#import <WebCore/CachedResourceLoader.h>
#import <WebCore/Chrome.h>
#import <WebCore/ColorMac.h>
#import <WebCore/ContextMenu.h>
#import <WebCore/ContextMenuController.h>
#import <WebCore/Document.h>
#import <WebCore/DocumentFragment.h>
#import <WebCore/DocumentMarkerController.h>
#import <WebCore/DragController.h>
#import <WebCore/Editor.h>
#import <WebCore/EditorDeleteAction.h>
#import <WebCore/Element.h>
#import <WebCore/EventHandler.h>
#import <WebCore/ExceptionHandlers.h>
#import <WebCore/FloatRect.h>
#import <WebCore/FocusController.h>
#import <WebCore/Frame.h>
#import <WebCore/FrameLoader.h>
#import <WebCore/FrameSelection.h>
#import <WebCore/FrameSnapshottingMac.h>
#import <WebCore/FrameView.h>
#import <WebCore/HTMLConverter.h>
#import <WebCore/HTMLNames.h>
#import <WebCore/HitTestResult.h>
#import <WebCore/Image.h>
#import <WebCore/KeyboardEvent.h>
#import <WebCore/LegacyWebArchive.h>
#import <WebCore/MIMETypeRegistry.h>
#import <WebCore/Page.h>
#import <WebCore/PlatformEventFactoryMac.h>
#import <WebCore/Range.h>
#import <WebCore/RenderView.h>
#import <WebCore/RenderWidget.h>
#import <WebCore/ResourceBuffer.h>
#import <WebCore/RunLoop.h>
#import <WebCore/RuntimeApplicationChecks.h>
#import <WebCore/SharedBuffer.h>
#import <WebCore/SimpleFontData.h>
#import <WebCore/StylePropertySet.h>
#import <WebCore/Text.h>
#import <WebCore/TextAlternativeWithRange.h>
#import <WebCore/WebCoreObjCExtras.h>
#import <WebCore/WebFontCache.h>
#import <WebCore/WebNSAttributedStringExtras.h>
#import <WebCore/markup.h>
#import <WebKit/DOM.h>
#import <WebKit/DOMExtensions.h>
#import <WebKit/DOMPrivate.h>
#import <WebKitSystemInterface.h>
#import <dlfcn.h>
#import <limits>
#import <runtime/InitializeThreading.h>
#import <wtf/MainThread.h>
#import <wtf/ObjcRuntimeExtras.h>
#if USE(ACCELERATED_COMPOSITING)
#import <QuartzCore/QuartzCore.h>
#endif
using namespace WebCore;
using namespace HTMLNames;
using namespace WTF;
using namespace std;
@interface WebMenuTarget : NSObject {
WebCore::ContextMenuController* _menuController;
}
+ (WebMenuTarget*)sharedMenuTarget;
- (WebCore::ContextMenuController*)menuController;
- (void)setMenuController:(WebCore::ContextMenuController*)menuController;
- (void)forwardContextMenuAction:(id)sender;
- (BOOL)validateMenuItem:(NSMenuItem *)item;
@end
static WebMenuTarget* target;
@implementation WebMenuTarget
+ (WebMenuTarget*)sharedMenuTarget
{
if (!target)
target = [[WebMenuTarget alloc] init];
return target;
}
- (WebCore::ContextMenuController*)menuController
{
return _menuController;
}
- (void)setMenuController:(WebCore::ContextMenuController*)menuController
{
_menuController = menuController;
}
- (void)forwardContextMenuAction:(id)sender
{
WebCore::ContextMenuItem item(WebCore::ActionType, static_cast<WebCore::ContextMenuAction>([sender tag]), [sender title]);
_menuController->contextMenuItemSelected(&item);
}
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
WebCore::ContextMenuItem coreItem(item);
ASSERT(_menuController->contextMenu());
_menuController->checkOrEnableIfNeeded(coreItem);
return coreItem.enabled();
}
@end
@interface NSWindow (BorderViewAccess)
- (NSView*)_web_borderView;
@end
@implementation NSWindow (BorderViewAccess)
- (NSView*)_web_borderView
{
return _borderView;
}
@end
@interface WebResponderChainSink : NSResponder {
NSResponder* _lastResponderInChain;
BOOL _receivedUnhandledCommand;
}
- (id)initWithResponderChain:(NSResponder *)chain;
- (void)detach;
- (BOOL)receivedUnhandledCommand;
@end
@interface WebLayerHostingFlippedView : NSView
@end
@implementation WebLayerHostingFlippedView
- (BOOL)isFlipped
{
return YES;
}
@end
@interface WebRootLayer : CALayer
@end
@implementation WebRootLayer
- (void)renderInContext:(CGContextRef)ctx
{
// AppKit calls -[CALayer renderInContext:] to render layer-backed views
// into bitmap contexts, but renderInContext: doesn't capture mask layers
// (<rdar://problem/9539526>), so we can't rely on it. Since our layer
// contents will have already been rendered by drawRect:, we can safely make
// this a NOOP.
}
@end
// if YES, do the standard NSView hit test (which can't give the right result when HTML overlaps a view)
static BOOL forceNSViewHitTest;
// if YES, do the "top WebHTMLView" hit test (which we'd like to do all the time but can't because of Java requirements [see bug 4349721])
static BOOL forceWebHTMLViewHitTest;
static WebHTMLView *lastHitView;
static bool needsCursorRectsSupportAtPoint(NSWindow* window, NSPoint point)
{
forceNSViewHitTest = YES;
NSView* view = [[window _web_borderView] hitTest:point];
forceNSViewHitTest = NO;
// WebHTMLView doesn't use cursor rects.
if ([view isKindOfClass:[WebHTMLView class]])
return false;
#if ENABLE(NETSCAPE_PLUGIN_API)
// Neither do NPAPI plug-ins.
if ([view isKindOfClass:[WebBaseNetscapePluginView class]])
return false;
#endif
// Non-Web content, WebPDFView, and WebKit plug-ins use normal cursor handling.
return true;
}
static IMP oldSetCursorForMouseLocationIMP;
// Overriding an internal method is a hack; <rdar://problem/7662987> tracks finding a better solution.
static void setCursor(NSWindow *self, SEL cmd, NSPoint point)
{
if (needsCursorRectsSupportAtPoint(self, point))
wtfCallIMP<id>(oldSetCursorForMouseLocationIMP, self, cmd, point);
}
extern "C" {
// Need to declare these attribute names because AppKit exports them but does not make them available in API or SPI headers.
extern NSString *NSMarkedClauseSegmentAttributeName;
extern NSString *NSTextInputReplacementRangeAttributeName;
}
@interface NSView (WebNSViewDetails)
- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView;
- (NSRect)_dirtyRect;
- (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants;
- (BOOL)_drawnByAncestor;
- (void)_invalidateGStatesForTree;
- (void)_propagateDirtyRectsToOpaqueAncestors;
- (void)_windowChangedKeyState;
@end
#if USE(ACCELERATED_COMPOSITING)
static IMP oldSetNeedsDisplayInRectIMP;
static void setNeedsDisplayInRect(NSView *self, SEL cmd, NSRect invalidRect)
{
if (![self _drawnByAncestor]) {
wtfCallIMP<id>(oldSetNeedsDisplayInRectIMP, self, cmd, invalidRect);
return;
}
static Class webFrameViewClass = [WebFrameView class];
WebFrameView *enclosingWebFrameView = (WebFrameView *)self;
while (enclosingWebFrameView && ![enclosingWebFrameView isKindOfClass:webFrameViewClass])
enclosingWebFrameView = (WebFrameView *)[enclosingWebFrameView superview];
if (!enclosingWebFrameView) {
wtfCallIMP<id>(oldSetNeedsDisplayInRectIMP, self, cmd, invalidRect);
return;
}
Frame* coreFrame = core([enclosingWebFrameView webFrame]);
FrameView* frameView = coreFrame ? coreFrame->view() : 0;
if (!frameView || !frameView->isEnclosedInCompositingLayer()) {
wtfCallIMP<id>(oldSetNeedsDisplayInRectIMP, self, cmd, invalidRect);
return;
}
NSRect invalidRectInWebFrameViewCoordinates = [enclosingWebFrameView convertRect:invalidRect fromView:self];
IntRect invalidRectInFrameViewCoordinates(invalidRectInWebFrameViewCoordinates);
if (![enclosingWebFrameView isFlipped])
invalidRectInFrameViewCoordinates.setY(frameView->frameRect().size().height() - invalidRectInFrameViewCoordinates.maxY());
frameView->invalidateRect(invalidRectInFrameViewCoordinates);
}
#endif // USE(ACCELERATED_COMPOSITING)
@interface NSApplication (WebNSApplicationDetails)
- (void)speakString:(NSString *)string;
@end
@interface NSWindow (WebNSWindowDetails)
- (id)_newFirstResponderAfterResigning;
@end
@interface NSAttributedString (WebNSAttributedStringDetails)
- (id)_initWithDOMRange:(DOMRange *)range;
- (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
@end
@interface NSSpellChecker (WebNSSpellCheckerDetails)
- (void)learnWord:(NSString *)word;
@end
// By imaging to a width a little wider than the available pixels,
// thin pages will be scaled down a little, matching the way they
// print in IE and Camino. This lets them use fewer sheets than they
// would otherwise, which is presumably why other browsers do this.
// Wide pages will be scaled down more than this.
const float _WebHTMLViewPrintingMinimumShrinkFactor = 1.25;
// This number determines how small we are willing to reduce the page content
// in order to accommodate the widest line. If the page would have to be
// reduced smaller to make the widest line fit, we just clip instead (this
// behavior matches MacIE and Mozilla, at least)
const float _WebHTMLViewPrintingMaximumShrinkFactor = 2;
#define AUTOSCROLL_INTERVAL 0.1f
// Any non-zero value will do, but using something recognizable might help us debug some day.
#define TRACKING_RECT_TAG 0xBADFACE
// FIXME: This constant is copied from AppKit's _NXSmartPaste constant.
#define WebSmartPastePboardType @"NeXT smart paste pasteboard type"
#define STANDARD_WEIGHT 5
#define MIN_BOLD_WEIGHT 7
#define STANDARD_BOLD_WEIGHT 9
// Fake URL scheme.
#define WebDataProtocolScheme @"webkit-fake-url"
// <rdar://problem/4985524> References to WebCoreScrollView as a subview of a WebHTMLView may be present
// in some NIB files, so NSUnarchiver must be still able to look up this now-unused class.
@interface WebCoreScrollView : NSScrollView
@end
@implementation WebCoreScrollView
@end
// We need this to be able to safely reference the CachedImage for the promised drag data
static CachedImageClient* promisedDataClient()
{
static CachedImageClient* staticCachedResourceClient = new CachedImageClient;
return staticCachedResourceClient;
}
@interface WebHTMLView (WebHTMLViewFileInternal)
- (BOOL)_imageExistsAtPaths:(NSArray *)paths;
- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText;
- (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard;
- (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
- (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard;
- (void)_removeMouseMovedObserverUnconditionally;
- (void)_removeSuperviewObservers;
- (void)_removeWindowObservers;
- (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
- (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
- (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action;
- (DOMRange *)_selectedRange;
- (BOOL)_shouldDeleteRange:(DOMRange *)range;
- (NSView *)_hitViewForEvent:(NSEvent *)event;
- (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString;
- (DOMRange *)_documentRange;
- (void)_setMouseDownEvent:(NSEvent *)event;
- (WebHTMLView *)_topHTMLView;
- (BOOL)_isTopHTMLView;
- (void)_web_setPrintingModeRecursive;
- (void)_web_setPrintingModeRecursiveAndAdjustViewSize;
- (void)_web_clearPrintingModeRecursive;
@end
@interface WebHTMLView (WebHTMLViewTextCheckingInternal)
- (void)orderFrontSubstitutionsPanel:(id)sender;
- (BOOL)smartInsertDeleteEnabled;
- (void)setSmartInsertDeleteEnabled:(BOOL)flag;
- (void)toggleSmartInsertDelete:(id)sender;
- (BOOL)isAutomaticQuoteSubstitutionEnabled;
- (void)setAutomaticQuoteSubstitutionEnabled:(BOOL)flag;
- (void)toggleAutomaticQuoteSubstitution:(id)sender;
- (BOOL)isAutomaticLinkDetectionEnabled;
- (void)setAutomaticLinkDetectionEnabled:(BOOL)flag;
- (void)toggleAutomaticLinkDetection:(id)sender;
- (BOOL)isAutomaticDashSubstitutionEnabled;
- (void)setAutomaticDashSubstitutionEnabled:(BOOL)flag;
- (void)toggleAutomaticDashSubstitution:(id)sender;
- (BOOL)isAutomaticTextReplacementEnabled;
- (void)setAutomaticTextReplacementEnabled:(BOOL)flag;
- (void)toggleAutomaticTextReplacement:(id)sender;
- (BOOL)isAutomaticSpellingCorrectionEnabled;
- (void)setAutomaticSpellingCorrectionEnabled:(BOOL)flag;
- (void)toggleAutomaticSpellingCorrection:(id)sender;
@end
@interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in a normal category and stop doing the forward declaration trick.
- (void)_setPrinting:(BOOL)printing minimumPageLogicalWidth:(float)minPageWidth logicalHeight:(float)minPageHeight originalPageWidth:(float)pageLogicalWidth originalPageHeight:(float)pageLogicalHeight maximumShrinkRatio:(float)maximumShrinkRatio adjustViewSize:(BOOL)adjustViewSize paginateScreenContent:(BOOL)paginateScreenContent;
- (void)_updateSecureInputState;
@end
@class NSTextInputContext;
@interface NSResponder (AppKitDetails)
- (NSTextInputContext *)inputContext;
@end
@interface NSObject (NSTextInputContextDetails)
- (BOOL)wantsToHandleMouseEvents;
- (BOOL)handleMouseEvent:(NSEvent *)event;
@end
@interface WebHTMLView (WebNSTextInputSupport) <NSTextInput>
- (void)_updateSelectionForInputManager;
@end
@interface WebHTMLView (WebEditingStyleSupport)
- (DOMCSSStyleDeclaration *)_emptyStyle;
- (NSString *)_colorAsString:(NSColor *)color;
@end
@interface NSView (WebHTMLViewFileInternal)
- (void)_web_addDescendantWebHTMLViewsToArray:(NSMutableArray *) array;
@end
@interface NSMutableDictionary (WebHTMLViewFileInternal)
- (void)_web_setObjectIfNotNil:(id)object forKey:(id)key;
@end
struct WebHTMLViewInterpretKeyEventsParameters {
KeyboardEvent* event;
bool eventInterpretationHadSideEffects;
bool shouldSaveCommands;
bool consumedByIM;
bool executingSavedKeypressCommands;
};
@interface WebHTMLViewPrivate : NSObject {
@public
BOOL closed;
BOOL ignoringMouseDraggedEvents;
BOOL printing;
BOOL paginateScreenContent;
BOOL observingMouseMovedNotifications;
BOOL observingSuperviewNotifications;
BOOL observingWindowNotifications;
id savedSubviews;
BOOL subviewsSetAside;
#if USE(ACCELERATED_COMPOSITING)
NSView *layerHostingView;
BOOL drawingIntoLayer;
#endif
NSEvent *mouseDownEvent; // Kept after handling the event.
BOOL handlingMouseDownEvent;
NSEvent *keyDownEvent; // Kept after handling the event.
// A WebHTMLView has a single input context, but we return nil when in non-editable content to avoid making input methods do their work.
// This state is saved each time selection changes, because computing it causes style recalc, which is not always safe to do.
BOOL exposeInputContext;
// Track whether the view has set a secure input state.
BOOL isInSecureInputState;
BOOL _forceUpdateSecureInputState;
NSPoint lastScrollPosition;
BOOL inScrollPositionChanged;
WebPluginController *pluginController;
NSString *toolTip;
NSToolTipTag lastToolTipTag;
id trackingRectOwner;
void *trackingRectUserData;
NSTimer *autoscrollTimer;
NSEvent *autoscrollTriggerEvent;
NSArray *pageRects;
NSMutableDictionary *highlighters;
WebTextCompletionController *completionController;
BOOL transparentBackground;
WebHTMLViewInterpretKeyEventsParameters* interpretKeyEventsParameters;
WebDataSource *dataSource;
WebCore::CachedImage* promisedDragTIFFDataSource;
SEL selectorForDoCommandBySelector;
NSTrackingArea *trackingAreaForNonKeyWindow;
#ifndef NDEBUG
BOOL enumeratingSubviews;
#endif
}
- (void)clear;
@end
static NSCellStateValue kit(TriState state)
{
switch (state) {
case FalseTriState:
return NSOffState;
case TrueTriState:
return NSOnState;
case MixedTriState:
return NSMixedState;
}
ASSERT_NOT_REACHED();
return NSOffState;
}
@implementation WebHTMLViewPrivate
+ (void)initialize
{
JSC::initializeThreading();
WTF::initializeMainThreadToProcessMainThread();
WebCore::RunLoop::initializeMainRunLoop();
WebCoreObjCFinalizeOnMainThread(self);
if (!oldSetCursorForMouseLocationIMP) {
Method setCursorMethod = class_getInstanceMethod([NSWindow class], @selector(_setCursorForMouseLocation:));
ASSERT(setCursorMethod);
oldSetCursorForMouseLocationIMP = method_setImplementation(setCursorMethod, (IMP)setCursor);
ASSERT(oldSetCursorForMouseLocationIMP);
}
#if USE(ACCELERATED_COMPOSITING)
if (!oldSetNeedsDisplayInRectIMP) {
Method setNeedsDisplayInRectMethod = class_getInstanceMethod([NSView class], @selector(setNeedsDisplayInRect:));
ASSERT(setNeedsDisplayInRectMethod);
oldSetNeedsDisplayInRectIMP = method_setImplementation(setNeedsDisplayInRectMethod, (IMP)setNeedsDisplayInRect);
ASSERT(oldSetNeedsDisplayInRectIMP);
}
#endif // USE(ACCELERATED_COMPOSITING)
}
- (void)dealloc
{
if (WebCoreObjCScheduleDeallocateOnMainThread([WebHTMLViewPrivate class], self))
return;
ASSERT(!autoscrollTimer);
ASSERT(!autoscrollTriggerEvent);
[mouseDownEvent release];
[keyDownEvent release];
[pluginController release];
[toolTip release];
[completionController release];
[dataSource release];
[highlighters release];
[trackingAreaForNonKeyWindow release];
if (promisedDragTIFFDataSource)
promisedDragTIFFDataSource->removeClient(promisedDataClient());
[super dealloc];
}
- (void)finalize
{
ASSERT_MAIN_THREAD();
if (promisedDragTIFFDataSource)
promisedDragTIFFDataSource->removeClient(promisedDataClient());
[super finalize];
}
- (void)clear
{
[mouseDownEvent release];
[keyDownEvent release];
[pluginController release];
[toolTip release];
[completionController release];
[dataSource release];
[highlighters release];
[trackingAreaForNonKeyWindow release];
if (promisedDragTIFFDataSource)
promisedDragTIFFDataSource->removeClient(promisedDataClient());
mouseDownEvent = nil;
keyDownEvent = nil;
pluginController = nil;
toolTip = nil;
completionController = nil;
dataSource = nil;
highlighters = nil;
trackingAreaForNonKeyWindow = nil;
promisedDragTIFFDataSource = 0;
#if USE(ACCELERATED_COMPOSITING)
layerHostingView = nil;
#endif
}
@end
@implementation WebHTMLView (WebHTMLViewFileInternal)
- (DOMRange *)_documentRange
{
return [[[self _frame] DOMDocument] _documentRange];
}
- (BOOL)_imageExistsAtPaths:(NSArray *)paths
{
NSEnumerator *enumerator = [paths objectEnumerator];
NSString *path;
while ((path = [enumerator nextObject]) != nil) {
NSString *MIMEType = WKGetMIMETypeForExtension([path pathExtension]);
if (MIMETypeRegistry::isSupportedImageResourceMIMEType(MIMEType))
return YES;
}
return NO;
}
- (WebDataSource *)_dataSource
{
return _private->dataSource;
}
- (WebView *)_webView
{
return [_private->dataSource _webView];
}
- (WebFrameView *)_frameView
{
return [[_private->dataSource webFrame] frameView];
}
- (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths
{
DOMDocumentFragment *fragment;
NSEnumerator *enumerator = [paths objectEnumerator];
NSMutableArray *domNodes = [[NSMutableArray alloc] init];
NSString *path;
while ((path = [enumerator nextObject]) != nil) {
// Non-image file types; _web_userVisibleString is appropriate here because this will
// be pasted as visible text.
NSString *url = [[[NSURL fileURLWithPath:path] _webkit_canonicalize] _web_userVisibleString];
[domNodes addObject:[[[self _frame] DOMDocument] createTextNode: url]];
}
fragment = [[self _frame] _documentFragmentWithNodesAsParagraphs:domNodes];
[domNodes release];
return [fragment firstChild] != nil ? fragment : nil;
}
+ (NSArray *)_excludedElementsForAttributedStringConversion
{
static NSArray *elements = nil;
if (elements == nil) {
elements = [[NSArray alloc] initWithObjects:
// Omit style since we want style to be inline so the fragment can be easily inserted.
@"style",
// Omit xml so the result is not XHTML.
@"xml",
// Omit tags that will get stripped when converted to a fragment anyway.
@"doctype", @"html", @"head", @"body",
// Omit deprecated tags.
@"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u",
// Omit object so no file attachments are part of the fragment.
@"object", nil];
CFRetain(elements);
}
return elements;
}
static NSURL* uniqueURLWithRelativePart(NSString *relativePart)
{
CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault);
NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef);
CFRelease(UUIDRef);
NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/%@", WebDataProtocolScheme, UUIDString, relativePart]];
CFRelease(UUIDString);
return URL;
}
- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard
inContext:(DOMRange *)context
allowPlainText:(BOOL)allowPlainText
{
NSArray *types = [pasteboard types];
DOMDocumentFragment *fragment = nil;
if ([types containsObject:WebArchivePboardType] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:WebArchivePboardType
inContext:context
subresources:0]))
return fragment;
if ([types containsObject:NSFilenamesPboardType] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:NSFilenamesPboardType
inContext:context
subresources:0]))
return fragment;
if ([types containsObject:NSHTMLPboardType] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:NSHTMLPboardType
inContext:context
subresources:0]))
return fragment;
if ([types containsObject:NSRTFDPboardType] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:NSRTFDPboardType
inContext:context
subresources:0]))
return fragment;
if ([types containsObject:NSRTFPboardType] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:NSRTFPboardType
inContext:context
subresources:0]))
return fragment;
if ([types containsObject:NSTIFFPboardType] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:NSTIFFPboardType
inContext:context
subresources:0]))
return fragment;
if ([types containsObject:NSPDFPboardType] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:NSPDFPboardType
inContext:context
subresources:0]))
return fragment;
if ([types containsObject:(NSString*)kUTTypePNG] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:(NSString*)kUTTypePNG
inContext:context
subresources:0]))
return fragment;
if ([types containsObject:NSURLPboardType] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:NSURLPboardType
inContext:context
subresources:0]))
return fragment;
if (allowPlainText && [types containsObject:NSStringPboardType] &&
(fragment = [self _documentFragmentFromPasteboard:pasteboard
forType:NSStringPboardType
inContext:context
subresources:0])) {
return fragment;
}
return nil;
}
- (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard
{
NSArray *types = [pasteboard types];
if ([types containsObject:NSStringPboardType])
return [[pasteboard stringForType:NSStringPboardType] precomposedStringWithCanonicalMapping];
NSAttributedString *attributedString = nil;
NSString *string;
if ([types containsObject:NSRTFDPboardType])
attributedString = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
if (attributedString == nil && [types containsObject:NSRTFPboardType])
attributedString = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
if (attributedString != nil) {
string = [[attributedString string] copy];
[attributedString release];
return [string autorelease];
}
if ([types containsObject:NSFilenamesPboardType]) {
string = [[pasteboard propertyListForType:NSFilenamesPboardType] componentsJoinedByString:@"\n"];
if (string != nil)
return string;
}
NSURL *URL;
if ((URL = [NSURL URLFromPasteboard:pasteboard])) {
string = [URL _web_userVisibleString];
if ([string length] > 0)
return string;
}
return nil;
}
- (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText
{
WebView *webView = [[self _webView] retain];
[webView _setInsertionPasteboard:pasteboard];
DOMRange *range = [self _selectedRange];
Frame* coreFrame = core([self _frame]);
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText];
if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:range givenAction:WebViewInsertActionPasted])
coreFrame->editor().pasteAsFragment(core(fragment), [self _canSmartReplaceWithPasteboard:pasteboard], false);
#else
// Mail is ignoring the frament passed to the delegate and creates a new one.
// We want to avoid creating the fragment twice.
if (applicationIsAppleMail()) {
if ([self _shouldInsertFragment:nil replacingDOMRange:range givenAction:WebViewInsertActionPasted]) {
DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText];
if (fragment)
coreFrame->editor().pasteAsFragment(core(fragment), [self _canSmartReplaceWithPasteboard:pasteboard], false);
}
} else {
DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText];
if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:range givenAction:WebViewInsertActionPasted])
coreFrame->editor().pasteAsFragment(core(fragment), [self _canSmartReplaceWithPasteboard:pasteboard], false);
}
#endif
[webView _setInsertionPasteboard:nil];
[webView release];
}
- (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard
{
WebView *webView = [[self _webView] retain];
[webView _setInsertionPasteboard:pasteboard];
NSString *text = [self _plainTextFromPasteboard:pasteboard];
if ([self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionPasted])
[[self _frame] _replaceSelectionWithText:text selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]];
[webView _setInsertionPasteboard:nil];
[webView release];
}
// This method is needed to support Mac OS X services.
- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard
{
Frame* coreFrame = core([self _frame]);
if (!coreFrame)
return NO;
if (coreFrame->selection()->isContentRichlyEditable())
[self _pasteWithPasteboard:pasteboard allowPlainText:YES];
else
[self _pasteAsPlainTextWithPasteboard:pasteboard];
return YES;
}
- (void)_removeMouseMovedObserverUnconditionally
{
if (!_private || !_private->observingMouseMovedNotifications)
return;
[[NSNotificationCenter defaultCenter] removeObserver:self name:WKMouseMovedNotification() object:nil];
_private->observingMouseMovedNotifications = false;
}
- (void)_removeSuperviewObservers
{
if (!_private || !_private->observingSuperviewNotifications)
return;
NSView *superview = [self superview];
if (!superview || ![self window])
return;
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:NSViewFrameDidChangeNotification object:superview];
[notificationCenter removeObserver:self name:NSViewBoundsDidChangeNotification object:superview];
_private->observingSuperviewNotifications = false;
}
- (void)_removeWindowObservers
{
if (!_private->observingWindowNotifications)
return;
NSWindow *window = [self window];
if (!window)
return;
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil];
[notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification object:nil];
[notificationCenter removeObserver:self name:WKWindowWillOrderOnScreenNotification() object:window];
[notificationCenter removeObserver:self name:WKWindowWillOrderOffScreenNotification() object:window];
[notificationCenter removeObserver:self name:NSWindowWillCloseNotification object:window];
_private->observingWindowNotifications = false;
}
- (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
{
WebView *webView = [self _webView];
DOMNode *child = [fragment firstChild];
if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]])
return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action];
return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action];
}
- (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
{
WebView *webView = [self _webView];
return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:range givenAction:action];
}
- (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action
{
return [self _shouldInsertText:text replacingDOMRange:[self _selectedRange] givenAction:action];
}
- (DOMRange *)_selectedRange
{
Frame* coreFrame = core([self _frame]);
return coreFrame ? kit(coreFrame->selection()->toNormalizedRange().get()) : nil;
}
- (BOOL)_shouldDeleteRange:(DOMRange *)range
{
Frame* coreFrame = core([self _frame]);
return coreFrame && coreFrame->editor().shouldDeleteRange(core(range));
}
- (NSView *)_hitViewForEvent:(NSEvent *)event
{
// Usually, we hack AK's hitTest method to catch all events at the topmost WebHTMLView.
// Callers of this method, however, want to query the deepest view instead.
forceNSViewHitTest = YES;
NSView *hitView = [(NSView *)[[self window] contentView] hitTest:[event locationInWindow]];
forceNSViewHitTest = NO;
return hitView;
}
- (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString
{
// Put HTML on the pasteboard.
if ([types containsObject:WebArchivePboardType]) {
if (RefPtr<LegacyWebArchive> coreArchive = LegacyWebArchive::createFromSelection(core([self _frame]))) {
if (RetainPtr<CFDataRef> data = coreArchive ? coreArchive->rawDataRepresentation() : 0)
[pasteboard setData:(NSData *)data.get() forType:WebArchivePboardType];
}
}
// Put the attributed string on the pasteboard (RTF/RTFD format).
if ([types containsObject:NSRTFDPboardType]) {
if (attributedString == nil) {
attributedString = [self selectedAttributedString];
}
NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
[pasteboard setData:RTFDData forType:NSRTFDPboardType];
}
if ([types containsObject:NSRTFPboardType]) {
if (!attributedString)
attributedString = [self selectedAttributedString];
if ([attributedString containsAttachments])
attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
[pasteboard setData:RTFData forType:NSRTFPboardType];
}
// Put plain string on the pasteboard.
if ([types containsObject:NSStringPboardType]) {
// Map &nbsp; to a plain old space because this is better for source code, other browsers do it,
// and because HTML forces you to do this any time you want two spaces in a row.
NSMutableString *s = [[self 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];
}
if ([self _canSmartCopyOrDelete] && [types containsObject:WebSmartPastePboardType]) {
[pasteboard setData:nil forType:WebSmartPastePboardType];
}
}
- (void)_setMouseDownEvent:(NSEvent *)event
{
ASSERT(!event || [event type] == NSLeftMouseDown || [event type] == NSRightMouseDown || [event type] == NSOtherMouseDown);
if (event == _private->mouseDownEvent)
return;
[event retain];
[_private->mouseDownEvent release];
_private->mouseDownEvent = event;
}
- (WebHTMLView *)_topHTMLView
{
// FIXME: this can fail if the dataSource is nil, which happens when the WebView is tearing down from the window closing.
WebHTMLView *view = (WebHTMLView *)[[[[_private->dataSource _webView] mainFrame] frameView] documentView];
ASSERT(!view || [view isKindOfClass:[WebHTMLView class]]);
return view;
}
- (BOOL)_isTopHTMLView
{
// FIXME: this should be a cached boolean that doesn't rely on _topHTMLView since that can fail (see _topHTMLView).
return self == [self _topHTMLView];
}
- (void)_web_setPrintingModeRecursive
{
[self _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
#ifndef NDEBUG
_private->enumeratingSubviews = YES;
#endif
NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init];
[self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews];
unsigned count = [descendantWebHTMLViews count];
for (unsigned i = 0; i < count; ++i)
[[descendantWebHTMLViews objectAtIndex:i] _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
[descendantWebHTMLViews release];
#ifndef NDEBUG
_private->enumeratingSubviews = NO;
#endif
}
- (void)_web_clearPrintingModeRecursive
{
[self _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
#ifndef NDEBUG
_private->enumeratingSubviews = YES;
#endif
NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init];
[self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews];
unsigned count = [descendantWebHTMLViews count];
for (unsigned i = 0; i < count; ++i)
[[descendantWebHTMLViews objectAtIndex:i] _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]];
[descendantWebHTMLViews release];
#ifndef NDEBUG
_private->enumeratingSubviews = NO;
#endif
}
- (void)_web_setPrintingModeRecursiveAndAdjustViewSize
{
[self _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
#ifndef NDEBUG
_private->enumeratingSubviews = YES;
#endif
NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init];
[self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews];
unsigned count = [descendantWebHTMLViews count];
for (unsigned i = 0; i < count; ++i)
[[descendantWebHTMLViews objectAtIndex:i] _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
[descendantWebHTMLViews release];
#ifndef NDEBUG
_private->enumeratingSubviews = NO;
#endif
}
@end
@implementation WebHTMLView (WebPrivate)
+ (NSArray *)supportedMIMETypes
{
return [WebHTMLRepresentation supportedMIMETypes];
}
+ (NSArray *)supportedImageMIMETypes
{
return [WebHTMLRepresentation supportedImageMIMETypes];
}
+ (NSArray *)supportedNonImageMIMETypes
{
return [WebHTMLRepresentation supportedNonImageMIMETypes];
}
+ (NSArray *)unsupportedTextMIMETypes
{
return [WebHTMLRepresentation unsupportedTextMIMETypes];
}
+ (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent
{
// This is a workaround for: <rdar://problem/2981619> NSResponder_Private should include notification for FlagsChanged
NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
location:[[flagsChangedEvent window] convertScreenToBase:[NSEvent mouseLocation]]
modifierFlags:[flagsChangedEvent modifierFlags]
timestamp:[flagsChangedEvent timestamp]
windowNumber:[flagsChangedEvent windowNumber]
context:[flagsChangedEvent context]
eventNumber:0 clickCount:0 pressure:0];
// Pretend it's a mouse move.
[[NSNotificationCenter defaultCenter]
postNotificationName:WKMouseMovedNotification() object:self
userInfo:[NSDictionary dictionaryWithObject:fakeEvent forKey:@"NSEvent"]];
}
- (id)_bridge
{
// This method exists to maintain compatibility with Leopard's Dictionary.app, since it
// calls _bridge to get access to convertNSRangeToDOMRange: and convertDOMRangeToNSRange:.
// Return the WebFrame, which implements the compatibility methods. <rdar://problem/6002160>
return [self _frame];
}
- (void)_updateMouseoverWithFakeEvent
{
NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
modifierFlags:[[NSApp currentEvent] modifierFlags]
timestamp:[NSDate timeIntervalSinceReferenceDate]
windowNumber:[[self window] windowNumber]
context:[[NSApp currentEvent] context]
eventNumber:0 clickCount:0 pressure:0];
[self _updateMouseoverWithEvent:fakeEvent];
}
- (void)_frameOrBoundsChanged
{
WebView *webView = [self _webView];
WebDynamicScrollBarsView *scrollView = [[[webView mainFrame] frameView] _scrollView];
NSPoint origin = [[self superview] bounds].origin;
if (!NSEqualPoints(_private->lastScrollPosition, origin) && ![scrollView inProgrammaticScroll]) {
if (Frame* coreFrame = core([self _frame])) {
if (FrameView* coreView = coreFrame->view()) {
_private->inScrollPositionChanged = YES;
coreView->scrollPositionChangedViaPlatformWidget();
_private->inScrollPositionChanged = NO;
}
}
[_private->completionController endRevertingChange:NO moveLeft:NO];
[[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[self _frameView]];
}
_private->lastScrollPosition = origin;
}
- (void)_setAsideSubviews
{
ASSERT(!_private->subviewsSetAside);
ASSERT(_private->savedSubviews == nil);
_private->savedSubviews = _subviews;
#if USE(ACCELERATED_COMPOSITING)
// We need to keep the layer-hosting view in the subviews, otherwise the layers flash.
if (_private->layerHostingView) {
NSArray* newSubviews = [[NSArray alloc] initWithObjects:_private->layerHostingView, nil];
_subviews = newSubviews;
} else
_subviews = nil;
#else
_subviews = nil;
#endif
_private->subviewsSetAside = YES;
}
- (void)_restoreSubviews
{
ASSERT(_private->subviewsSetAside);
#if USE(ACCELERATED_COMPOSITING)
if (_private->layerHostingView) {
[_subviews release];
_subviews = _private->savedSubviews;
} else {
ASSERT(_subviews == nil);
_subviews = _private->savedSubviews;
}
#else
ASSERT(_subviews == nil);
_subviews = _private->savedSubviews;
#endif
_private->savedSubviews = nil;
_private->subviewsSetAside = NO;
}
#ifndef NDEBUG
- (void)didAddSubview:(NSView *)subview
{
if (_private->enumeratingSubviews)
LOG(View, "A view of class %s was added during subview enumeration for layout or printing mode change. This view might paint without first receiving layout.", object_getClassName([subview class]));
}
#endif
- (void)viewWillDraw
{
// On window close we will be called when the datasource is nil, then hit an assert in _topHTMLView
// So check if the dataSource is nil before calling [self _isTopHTMLView], this can be removed
// once the FIXME in _isTopHTMLView is fixed.
if (_private->dataSource && [self _isTopHTMLView])
[self _web_updateLayoutAndStyleIfNeededRecursive];
[super viewWillDraw];
}
// Don't let AppKit even draw subviews. We take care of that.
- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView
{
// This helps when we print as part of a larger print process.
// If the WebHTMLView itself is what we're printing, then we will never have to do this.
BOOL wasInPrintingMode = _private->printing;
BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
if (isPrinting) {
if (!wasInPrintingMode)
[self _web_setPrintingModeRecursive];
else
[self _web_updateLayoutAndStyleIfNeededRecursive];
} else if (wasInPrintingMode)
[self _web_clearPrintingModeRecursive];
// There are known cases where -viewWillDraw is not called on all views being drawn.
// See <rdar://problem/6964278> for example. Performing layout at this point prevents us from
// trying to paint without layout (which WebCore now refuses to do, instead bailing out without
// drawing at all), but we may still fail to update any regions dirtied by the layout which are
// not already dirty.
if ([self _needsLayout]) {
NSInteger rectCount;
[self getRectsBeingDrawn:0 count:&rectCount];
if (rectCount) {
LOG_ERROR("View needs layout. Either -viewWillDraw wasn't called or layout was invalidated during the display operation. Performing layout now.");
[self _web_updateLayoutAndStyleIfNeededRecursive];
}
}
[self _setAsideSubviews];
[super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView];
[self _restoreSubviews];
if (wasInPrintingMode != isPrinting) {
if (wasInPrintingMode)
[self _web_setPrintingModeRecursive];
else
[self _web_clearPrintingModeRecursive];
}
}
// Don't let AppKit even draw subviews. We take care of that.
- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect
{
BOOL needToSetAsideSubviews = !_private->subviewsSetAside;
BOOL wasInPrintingMode = _private->printing;
BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
if (needToSetAsideSubviews) {
// This helps when we print as part of a larger print process.
// If the WebHTMLView itself is what we're printing, then we will never have to do this.
if (isPrinting) {
if (!wasInPrintingMode)
[self _web_setPrintingModeRecursive];
else
[self _web_updateLayoutAndStyleIfNeededRecursive];
} else if (wasInPrintingMode)
[self _web_clearPrintingModeRecursive];
[self _setAsideSubviews];
}
[super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect];
if (needToSetAsideSubviews) {
if (wasInPrintingMode != isPrinting) {
if (wasInPrintingMode)
[self _web_setPrintingModeRecursive];
else
[self _web_clearPrintingModeRecursive];
}
[self _restoreSubviews];
}
}
// Don't let AppKit even draw subviews. We take care of that.
- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView
{
[self _setAsideSubviews];
[super _recursive:recurse displayRectIgnoringOpacity:displayRect inContext:context topView:topView];
[self _restoreSubviews];
}
- (BOOL)_insideAnotherHTMLView
{
return self != [self _topHTMLView];
}
static BOOL isQuickLookEvent(NSEvent *event)
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
const int kCGSEventSystemSubtypeHotKeyCombinationReleased = 9;
return [event type] == NSSystemDefined && [event subtype] == kCGSEventSystemSubtypeHotKeyCombinationReleased && [event data1] == 'lkup';
#else
return NO;
#endif
}
- (NSView *)hitTest:(NSPoint)point
{
// WebHTMLView objects handle all events for objects inside them.
// To get those events, we prevent hit testing from AppKit.
// But there are three exceptions to this:
// 1) For right mouse clicks and control clicks we don't yet have an implementation
// that works for nested views, so we let the hit testing go through the
// standard NSView code path (needs to be fixed, see bug 4361618).
// 2) Java depends on doing a hit test inside it's mouse moved handling,
// so we let the hit testing go through the standard NSView code path
// when the current event is a mouse move (except when we are calling
// from _updateMouseoverWithEvent, so we have to use a global,
// forceWebHTMLViewHitTest, for that)
// 3) The acceptsFirstMouse: and shouldDelayWindowOrderingForEvent: methods
// both need to figure out which view to check with inside the WebHTMLView.
// They use a global to change the behavior of hitTest: so they can get the
// right view. The global is forceNSViewHitTest and the method they use to
// do the hit testing is _hitViewForEvent:. (But this does not work correctly
// when there is HTML overlapping the view, see bug 4361626)
// 4) NSAccessibilityHitTest relies on this for checking the cursor position.
// Our check for that is whether the event is NSFlagsChanged. This works
// for VoiceOver's Control-Option-F5 command (move focus to item under cursor)
// and Dictionary's Command-Control-D (open dictionary popup for item under cursor).
// This is of course a hack.
if (_private->closed)
return nil;
BOOL captureHitsOnSubviews;
if (forceNSViewHitTest)
captureHitsOnSubviews = NO;
else if (forceWebHTMLViewHitTest)
captureHitsOnSubviews = YES;
else {
// FIXME: Why doesn't this include mouse entered/exited events, or other mouse button events?
NSEvent *event = [[self window] currentEvent];
captureHitsOnSubviews = !([event type] == NSMouseMoved
|| [event type] == NSRightMouseDown
|| ([event type] == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask) != 0)
|| [event type] == NSFlagsChanged
|| isQuickLookEvent(event));
}
if (!captureHitsOnSubviews) {
NSView* hitView = [super hitTest:point];
#if USE(ACCELERATED_COMPOSITING)
if (_private && hitView == _private->layerHostingView)
hitView = self;
#endif
return hitView;
}
if ([[self superview] mouse:point inRect:[self frame]])
return self;
return nil;
}
- (void)_clearLastHitViewIfSelf
{
if (lastHitView == self)
lastHitView = nil;
}
- (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside
{
ASSERT(_private->trackingRectOwner == nil);
_private->trackingRectOwner = owner;
_private->trackingRectUserData = data;
return TRACKING_RECT_TAG;
}
- (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag
{
ASSERT(tag == 0 || tag == TRACKING_RECT_TAG);
ASSERT(_private->trackingRectOwner == nil);
_private->trackingRectOwner = owner;
_private->trackingRectUserData = data;
return TRACKING_RECT_TAG;
}
- (void)_addTrackingRects:(NSRect *)rects owner:(id)owner userDataList:(void **)userDataList assumeInsideList:(BOOL *)assumeInsideList trackingNums:(NSTrackingRectTag *)trackingNums count:(int)count
{
ASSERT(count == 1);
ASSERT(trackingNums[0] == 0 || trackingNums[0] == TRACKING_RECT_TAG);
ASSERT(_private->trackingRectOwner == nil);
_private->trackingRectOwner = owner;
_private->trackingRectUserData = userDataList[0];
trackingNums[0] = TRACKING_RECT_TAG;
}
- (void)removeTrackingRect:(NSTrackingRectTag)tag
{
if (tag == 0)
return;
if (_private && (tag == TRACKING_RECT_TAG)) {
_private->trackingRectOwner = nil;
return;
}
if (_private && (tag == _private->lastToolTipTag)) {
[super removeTrackingRect:tag];
_private->lastToolTipTag = 0;
return;
}
// If any other tracking rect is being removed, we don't know how it was created
// and it's possible there's a leak involved (see 3500217)
ASSERT_NOT_REACHED();
}
- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count
{
int i;
for (i = 0; i < count; ++i) {
int tag = tags[i];
if (tag == 0)
continue;
ASSERT(tag == TRACKING_RECT_TAG);
if (_private != nil) {
_private->trackingRectOwner = nil;
}
}
}
- (void)_sendToolTipMouseExited
{
// Nothing matters except window, trackingNumber, and userData.
NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
location:NSMakePoint(0, 0)
modifierFlags:0
timestamp:0
windowNumber:[[self window] windowNumber]
context:NULL
eventNumber:0
trackingNumber:TRACKING_RECT_TAG
userData:_private->trackingRectUserData];
[_private->trackingRectOwner mouseExited:fakeEvent];
}
- (void)_sendToolTipMouseEntered
{
// Nothing matters except window, trackingNumber, and userData.
NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
location:NSMakePoint(0, 0)
modifierFlags:0
timestamp:0
windowNumber:[[self window] windowNumber]
context:NULL
eventNumber:0
trackingNumber:TRACKING_RECT_TAG
userData:_private->trackingRectUserData];
[_private->trackingRectOwner mouseEntered:fakeEvent];
}
- (void)_setToolTip:(NSString *)string
{
NSString *toolTip = [string length] == 0 ? nil : string;
NSString *oldToolTip = _private->toolTip;
if ((toolTip == nil || oldToolTip == nil) ? toolTip == oldToolTip : [toolTip isEqualToString:oldToolTip]) {
return;
}
if (oldToolTip) {
[self _sendToolTipMouseExited];
[oldToolTip release];
}
_private->toolTip = [toolTip copy];
if (toolTip) {
// See radar 3500217 for why we remove all tooltips rather than just the single one we created.
[self removeAllToolTips];
NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
_private->lastToolTipTag = [self addToolTipRect:wideOpenRect owner:self userData:NULL];
[self _sendToolTipMouseEntered];
}
}
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
{
return [[_private->toolTip copy] autorelease];
}
static bool mouseEventIsPartOfClickOrDrag(NSEvent *event)
{
switch ([event type]) {
case NSLeftMouseDown:
case NSLeftMouseUp:
case NSLeftMouseDragged:
case NSRightMouseDown:
case NSRightMouseUp:
case NSRightMouseDragged:
case NSOtherMouseDown:
case NSOtherMouseUp:
case NSOtherMouseDragged:
return true;
default:
return false;
}
}
- (void)_updateMouseoverWithEvent:(NSEvent *)event
{
if (_private->closed)
return;
NSView *contentView = [[event window] contentView];
NSPoint locationForHitTest = [[contentView superview] convertPoint:[event locationInWindow] fromView:nil];
forceWebHTMLViewHitTest = YES;
NSView *hitView = [contentView hitTest:locationForHitTest];
forceWebHTMLViewHitTest = NO;
WebHTMLView *view = nil;
if ([hitView isKindOfClass:[WebHTMLView class]])
view = (WebHTMLView *)hitView;
if (view)
[view retain];
if (lastHitView != view && lastHitView && [lastHitView _frame]) {
// If we are moving out of a view (or frame), let's pretend the mouse moved
// all the way out of that view. But we have to account for scrolling, because
// WebCore doesn't understand our clipping.
NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect];
float yScroll = visibleRect.origin.y;
float xScroll = visibleRect.origin.x;
NSEvent *event = [NSEvent mouseEventWithType:NSMouseMoved
location:NSMakePoint(-1 - xScroll, -1 - yScroll)
modifierFlags:[[NSApp currentEvent] modifierFlags]
timestamp:[NSDate timeIntervalSinceReferenceDate]
windowNumber:[[view window] windowNumber]
context:[[NSApp currentEvent] context]
eventNumber:0 clickCount:0 pressure:0];
if (Frame* lastHitCoreFrame = core([lastHitView _frame]))
lastHitCoreFrame->eventHandler()->mouseMoved(event);
}
lastHitView = view;
if (view) {
if (Frame* coreFrame = core([view _frame])) {
// We need to do a full, normal hit test during this mouse event if the page is active or if a mouse
// button is currently pressed. It is possible that neither of those things will be true on Lion and
// newer when legacy scrollbars are enabled, because then WebKit receives mouse events all the time.
// If it is one of those cases where the page is not active and the mouse is not pressed, then we can
// fire a much more restricted and efficient scrollbars-only version of the event.
if ([[self window] isKeyWindow] || mouseEventIsPartOfClickOrDrag(event))
coreFrame->eventHandler()->mouseMoved(event);
else
coreFrame->eventHandler()->passMouseMovedEventToScrollbars(event);
}
[view release];
}
}
+ (NSArray *)_insertablePasteboardTypes
{
static NSArray *types = nil;
if (!types) {
types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPDFPboardType,
NSURLPboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, NSColorPboardType, kUTTypePNG, nil];
CFRetain(types);
}
return types;
}
+ (NSArray *)_selectionPasteboardTypes
{
// FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423).
return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
}
- (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard
{
[self setPromisedDragTIFFDataSource:0];
}
- (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type
{
if ([type isEqual:NSRTFDPboardType] && [[pasteboard types] containsObject:WebArchivePboardType]) {
WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]];
[pasteboard _web_writePromisedRTFDFromArchive:archive containsImage:[[pasteboard types] containsObject:NSTIFFPboardType]];
[archive release];
} else if ([type isEqual:NSTIFFPboardType] && [self promisedDragTIFFDataSource]) {
if (Image* image = [self promisedDragTIFFDataSource]->image())
[pasteboard setData:(NSData *)image->getTIFFRepresentation() forType:NSTIFFPboardType];
[self setPromisedDragTIFFDataSource:0];
}
}
- (void)_handleAutoscrollForMouseDragged:(NSEvent *)event
{
[self autoscroll:event];
[self _startAutoscrollTimer:event];
}
- (WebPluginController *)_pluginController
{
return _private->pluginController;
}
- (void)_layoutForPrinting
{
// Set printing mode temporarily so we can adjust the size of the view. This will allow
// AppKit's pagination code to use the correct height for the page content. Leaving printing
// mode on indefinitely would interfere with Mail's printing mechanism (at least), so we just
// turn it off again after adjusting the size.
[self _web_setPrintingModeRecursiveAndAdjustViewSize];
[self _web_clearPrintingModeRecursive];
}
- (void)_smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)rangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString
{
if (!pasteString || !rangeToReplace || ![[self _webView] smartInsertDeleteEnabled]) {
if (beforeString)
*beforeString = nil;
if (afterString)
*afterString = nil;
return;
}
[[self _frame] _smartInsertForString:pasteString replacingRange:rangeToReplace beforeString:beforeString afterString:afterString];
}
- (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard
{
return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType];
}
- (void)_startAutoscrollTimer:(NSEvent *)triggerEvent
{
if (_private->autoscrollTimer == nil) {
_private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL
target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES] retain];
_private->autoscrollTriggerEvent = [triggerEvent retain];
}
}
// FIXME: _selectionRect is deprecated in favor of selectionRect, which is in protocol WebDocumentSelection.
// We can't remove this yet because it's still in use by Mail.
- (NSRect)_selectionRect
{
return [self selectionRect];
}
- (void)_stopAutoscrollTimer
{
NSTimer *timer = _private->autoscrollTimer;
_private->autoscrollTimer = nil;
[_private->autoscrollTriggerEvent release];
_private->autoscrollTriggerEvent = nil;
[timer invalidate];
[timer release];
}
- (void)_autoscroll
{
// Guarantee that the autoscroll timer is invalidated, even if we don't receive
// a mouse up event.
BOOL isStillDown = CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft);
if (!isStillDown){
[self _stopAutoscrollTimer];
return;
}
NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
modifierFlags:[[NSApp currentEvent] modifierFlags]
timestamp:[NSDate timeIntervalSinceReferenceDate]
windowNumber:[[self window] windowNumber]
context:[[NSApp currentEvent] context]
eventNumber:0 clickCount:0 pressure:0];
[self mouseDragged:fakeEvent];
}
- (BOOL)_canEdit
{
Frame* coreFrame = core([self _frame]);
return coreFrame && coreFrame->editor().canEdit();
}
- (BOOL)_canEditRichly
{
Frame* coreFrame = core([self _frame]);
return coreFrame && coreFrame->editor().canEditRichly();
}
- (BOOL)_canAlterCurrentSelection
{
return [self _hasSelectionOrInsertionPoint] && [self _isEditable];
}
- (BOOL)_hasSelection
{
Frame* coreFrame = core([self _frame]);
return coreFrame && coreFrame->selection()->isRange();
}
- (BOOL)_hasSelectionOrInsertionPoint
{
Frame* coreFrame = core([self _frame]);
return coreFrame && coreFrame->selection()->isCaretOrRange();
}
- (BOOL)_hasInsertionPoint
{
Frame* coreFrame = core([self _frame]);
return coreFrame && coreFrame->selection()->isCaret();
}
- (BOOL)_isEditable
{
Frame* coreFrame = core([self _frame]);
return coreFrame && coreFrame->selection()->isContentEditable();
}
- (BOOL)_transparentBackground
{
return _private->transparentBackground;
}
- (void)_setTransparentBackground:(BOOL)f
{
_private->transparentBackground = f;
}
- (NSImage *)_selectionDraggingImage
{
if (![self _hasSelection])
return nil;
NSImage *dragImage = selectionImage(core([self _frame]));
[dragImage _web_dissolveToFraction:WebDragImageAlpha];
return dragImage;
}
- (NSRect)_selectionDraggingRect
{
// Mail currently calls this method. We can eliminate it when Mail no longer calls it.
return [self selectionRect];
}
- (DOMNode *)_insertOrderedList
{
Frame* coreFrame = core([self _frame]);
return coreFrame ? kit(coreFrame->editor().insertOrderedList().get()) : nil;
}
- (DOMNode *)_insertUnorderedList
{
Frame* coreFrame = core([self _frame]);
return coreFrame ? kit(coreFrame->editor().insertUnorderedList().get()) : nil;
}
- (BOOL)_canIncreaseSelectionListLevel
{
Frame* coreFrame = core([self _frame]);
return coreFrame && coreFrame->editor().canIncreaseSelectionListLevel();
}
- (BOOL)_canDecreaseSelectionListLevel
{
Frame* coreFrame = core([self _frame]);
return coreFrame && coreFrame->editor().canDecreaseSelectionListLevel();
}
- (DOMNode *)_increaseSelectionListLevel
{
Frame* coreFrame = core([self _frame]);
return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevel().get()) : nil;
}
- (DOMNode *)_increaseSelectionListLevelOrdered
{
Frame* coreFrame = core([self _frame]);
return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevelOrdered().get()) : nil;
}
- (DOMNode *)_increaseSelectionListLevelUnordered
{
Frame* coreFrame = core([self _frame]);
return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevelUnordered().get()) : nil;
}
- (void)_decreaseSelectionListLevel
{
Frame* coreFrame = core([self _frame]);
if (coreFrame)
coreFrame->editor().decreaseSelectionListLevel();
}
- (void)_setHighlighter:(id<WebHTMLHighlighter>)highlighter ofType:(NSString*)type
{
if (!_private->highlighters)
_private->highlighters = [[NSMutableDictionary alloc] init];
[_private->highlighters setObject:highlighter forKey:type];
}
- (void)_removeHighlighterOfType:(NSString*)type
{
[_private->highlighters removeObjectForKey:type];
}
- (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard
{
ASSERT([self _hasSelection]);
NSArray *types = [self pasteboardTypesForSelection];
// Don't write RTFD to the pasteboard when the copied attributed string has no attachments.
NSAttributedString *attributedString = [self selectedAttributedString];
NSMutableArray *mutableTypes = nil;
if (![attributedString containsAttachments]) {
mutableTypes = [types mutableCopy];
[mutableTypes removeObject:NSRTFDPboardType];
types = mutableTypes;
}
[pasteboard declareTypes:types owner:[self _topHTMLView]];
[self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:attributedString];
[mutableTypes release];
}
- (void)close
{
// Check for a nil _private here in case we were created with initWithCoder. In that case, the WebView is just throwing
// out the archived WebHTMLView and recreating a new one if needed. So close doesn't need to do anything in that case.
if (!_private || _private->closed)
return;
_private->closed = YES;
[self _clearLastHitViewIfSelf];
[self _removeMouseMovedObserverUnconditionally];
[self _removeWindowObservers];
[self _removeSuperviewObservers];
[_private->pluginController destroyAllPlugins];
[_private->pluginController setDataSource:nil];
// remove tooltips before clearing _private so removeTrackingRect: will work correctly
[self removeAllToolTips];
if (_private->isInSecureInputState) {
DisableSecureEventInput();
_private->isInSecureInputState = NO;
}
[_private clear];
}
- (BOOL)_hasHTMLDocument
{
Frame* coreFrame = core([self _frame]);
if (!coreFrame)
return NO;
Document* document = coreFrame->document();
return document && document->isHTMLDocument();
}
- (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard
forType:(NSString *)pboardType
inContext:(DOMRange *)context
subresources:(NSArray **)subresources
{
if (pboardType == WebArchivePboardType) {
WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]];
if (subresources)
*subresources = [archive subresources];
DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithArchive:archive];
[archive release];
return fragment;
}
if (pboardType == NSFilenamesPboardType)
return [self _documentFragmentWithPaths:[pasteboard propertyListForType:NSFilenamesPboardType]];
if (pboardType == NSHTMLPboardType) {
NSString *HTMLString = [pasteboard stringForType:NSHTMLPboardType];
// This is a hack to make Microsoft's HTML pasteboard data work. See 3778785.
if ([HTMLString hasPrefix:@"Version:"]) {
NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound)
HTMLString = [HTMLString substringFromIndex:range.location];
}
if ([HTMLString length] == 0)
return nil;
return [[self _frame] _documentFragmentWithMarkupString:HTMLString baseURLString:nil];
}
// The _hasHTMLDocument clause here is a workaround for a bug in NSAttributedString: Radar 5052369.
// If we call _documentFromRange on an XML document we'll get "setInnerHTML: method not found".
// FIXME: Remove this once bug 5052369 is fixed.
if ([self _hasHTMLDocument] && (pboardType == NSRTFPboardType || pboardType == NSRTFDPboardType)) {
NSAttributedString *string = nil;
if (pboardType == NSRTFDPboardType)
string = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
if (string == nil)
string = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL];
if (string == nil)
return nil;
NSDictionary *documentAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
[[self class] _excludedElementsForAttributedStringConversion], NSExcludedElementsDocumentAttribute,
self, @"WebResourceHandler", nil];
NSArray *s;
BOOL wasDeferringCallbacks = [[self _webView] defersCallbacks];
if (!wasDeferringCallbacks)
[[self _webView] setDefersCallbacks:YES];
DOMDocumentFragment *fragment = [string _documentFromRange:NSMakeRange(0, [string length])
document:[[self _frame] DOMDocument]
documentAttributes:documentAttributes
subresources:&s];
if (subresources)
*subresources = s;
NSEnumerator *e = [s objectEnumerator];
WebResource *r;
while ((r = [e nextObject]))
[[self _dataSource] addSubresource:r];
if (!wasDeferringCallbacks)
[[self _webView] setDefersCallbacks:NO];
[documentAttributes release];
[string release];
return fragment;
}
if (pboardType == NSTIFFPboardType) {
WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSTIFFPboardType]
URL:uniqueURLWithRelativePart(@"image.tiff")
MIMEType:@"image/tiff"
textEncodingName:nil
frameName:nil];
DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
[resource release];
return fragment;
}
if (pboardType == NSPDFPboardType) {
WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPDFPboardType]
URL:uniqueURLWithRelativePart(@"application.pdf")
MIMEType:@"application/pdf"
textEncodingName:nil
frameName:nil];
DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
[resource release];
return fragment;
}
if ([pboardType isEqualToString:(NSString*)kUTTypePNG]) {
WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:(NSString*)kUTTypePNG]
URL:uniqueURLWithRelativePart(@"image.png")
MIMEType:@"image/png"
textEncodingName:nil
frameName:nil];
DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
[resource release];
return fragment;
}
if (pboardType == NSURLPboardType) {
NSURL *URL = [NSURL URLFromPasteboard:pasteboard];
DOMDocument* document = [[self _frame] DOMDocument];
ASSERT(document);
if (!document)
return nil;
DOMHTMLAnchorElement *anchor = (DOMHTMLAnchorElement *)[document createElement:@"a"];
NSString *URLString = [URL _web_originalDataAsString]; // Original data is ASCII-only, so there is no need to precompose.
if ([URLString length] == 0)
return nil;
NSString *URLTitleString = [[pasteboard stringForType:WebURLNamePboardType] precomposedStringWithCanonicalMapping];
DOMText *text = [document createTextNode:URLTitleString];
[anchor setHref:URLString];
[anchor appendChild:text];
DOMDocumentFragment *fragment = [document createDocumentFragment];
[fragment appendChild:anchor];
return fragment;
}
if (pboardType == NSStringPboardType)
return kit(createFragmentFromText(core(context), [[pasteboard stringForType:NSStringPboardType] precomposedStringWithCanonicalMapping]).get());
return nil;
}
#if ENABLE(NETSCAPE_PLUGIN_API)
- (void)_pauseNullEventsForAllNetscapePlugins
{
NSArray *subviews = [self subviews];
unsigned int subviewCount = [subviews count];
unsigned int subviewIndex;
for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) {
NSView *subview = [subviews objectAtIndex:subviewIndex];
if ([subview isKindOfClass:[WebBaseNetscapePluginView class]])
[(WebBaseNetscapePluginView *)subview stopTimers];
}
}
#endif
#if ENABLE(NETSCAPE_PLUGIN_API)
- (void)_resumeNullEventsForAllNetscapePlugins
{
NSArray *subviews = [self subviews];
unsigned int subviewCount = [subviews count];
unsigned int subviewIndex;
for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) {
NSView *subview = [subviews objectAtIndex:subviewIndex];
if ([subview isKindOfClass:[WebBaseNetscapePluginView class]])
[(WebBaseNetscapePluginView *)subview restartTimers];
}
}
#endif
- (BOOL)_isUsingAcceleratedCompositing
{
#if USE(ACCELERATED_COMPOSITING)
return _private->layerHostingView != nil;
#else
return NO;
#endif
}
- (NSView *)_compositingLayersHostingView
{
#if USE(ACCELERATED_COMPOSITING)
return _private->layerHostingView;
#else
return 0;
#endif
}
- (BOOL)_isInPrintMode
{
return _private->printing;
}
- (BOOL)_beginPrintModeWithMinimumPageWidth:(CGFloat)minimumPageWidth height:(CGFloat)minimumPageHeight maximumPageWidth:(CGFloat)maximumPageWidth
{
Frame* frame = core([self _frame]);
if (!frame)
return NO;
if (frame->document() && frame->document()->isFrameSet()) {
minimumPageWidth = 0;
minimumPageHeight = 0;
}
float maximumShrinkRatio = 0;
if (minimumPageWidth > 0.0)
maximumShrinkRatio = maximumPageWidth / minimumPageWidth;
[self _setPrinting:YES minimumPageLogicalWidth:minimumPageWidth logicalHeight:minimumPageHeight originalPageWidth:minimumPageWidth originalPageHeight:minimumPageHeight maximumShrinkRatio:maximumShrinkRatio adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
return YES;
}
- (BOOL)_beginPrintModeWithPageWidth:(float)pageWidth height:(float)pageHeight shrinkToFit:(BOOL)shrinkToFit
{
Frame* frame = core([self _frame]);
if (!frame)
return NO;
Document* document = frame->document();
bool isHorizontal = !document || !document->renderView() || document->renderView()->style()->isHorizontalWritingMode();
float pageLogicalWidth = isHorizontal ? pageWidth : pageHeight;
float pageLogicalHeight = isHorizontal ? pageHeight : pageWidth;
FloatSize minLayoutSize(pageLogicalWidth, pageLogicalHeight);
float maximumShrinkRatio = 1;
// If we are a frameset just print with the layout we have onscreen, otherwise relayout
// according to the page width.
if (shrinkToFit && (!frame->document() || !frame->document()->isFrameSet())) {
minLayoutSize = frame->resizePageRectsKeepingRatio(FloatSize(pageLogicalWidth, pageLogicalHeight), FloatSize(pageLogicalWidth * _WebHTMLViewPrintingMinimumShrinkFactor, pageLogicalHeight * _WebHTMLViewPrintingMinimumShrinkFactor));
maximumShrinkRatio = _WebHTMLViewPrintingMaximumShrinkFactor / _WebHTMLViewPrintingMinimumShrinkFactor;
}
[self _setPrinting:YES minimumPageLogicalWidth:minLayoutSize.width() logicalHeight:minLayoutSize.height() originalPageWidth:pageLogicalWidth originalPageHeight:pageLogicalHeight maximumShrinkRatio:maximumShrinkRatio adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
return YES;
}
- (void)_endPrintMode
{
[self _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
}
- (BOOL)_isInScreenPaginationMode
{
return _private->paginateScreenContent;
}
- (BOOL)_beginScreenPaginationModeWithPageSize:(CGSize)pageSize shrinkToFit:(BOOL)shrinkToFit
{
Frame* frame = core([self _frame]);
if (!frame)
return NO;
Document* document = frame->document();
bool isHorizontal = !document || !document->renderView() || document->renderView()->style()->isHorizontalWritingMode();
float pageLogicalWidth = isHorizontal ? pageSize.width : pageSize.height;
float pageLogicalHeight = isHorizontal ? pageSize.height : pageSize.width;
FloatSize minLayoutSize(pageLogicalWidth, pageLogicalHeight);
float maximumShrinkRatio = 1;
// If we are a frameset just print with the layout we have onscreen, otherwise relayout
// according to the page width.
if (shrinkToFit && (!frame->document() || !frame->document()->isFrameSet())) {
minLayoutSize = frame->resizePageRectsKeepingRatio(FloatSize(pageLogicalWidth, pageLogicalHeight), FloatSize(pageLogicalWidth * _WebHTMLViewPrintingMinimumShrinkFactor, pageLogicalHeight * _WebHTMLViewPrintingMinimumShrinkFactor));
maximumShrinkRatio = _WebHTMLViewPrintingMaximumShrinkFactor / _WebHTMLViewPrintingMinimumShrinkFactor;
}
[self _setPrinting:[self _isInPrintMode] minimumPageLogicalWidth:minLayoutSize.width() logicalHeight:minLayoutSize.height() originalPageWidth:pageLogicalWidth originalPageHeight:pageLogicalHeight maximumShrinkRatio:maximumShrinkRatio adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]];
return YES;
}
- (void)_endScreenPaginationMode
{
[self _setPrinting:[self _isInPrintMode] minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:NO];
}
- (CGFloat)_adjustedBottomOfPageWithTop:(CGFloat)top bottom:(CGFloat)bottom limit:(CGFloat)bottomLimit
{
Frame* frame = core([self _frame]);
if (!frame)
return bottom;
FrameView* view = frame->view();
if (!view)
return bottom;
float newBottom;
view->adjustPageHeightDeprecated(&newBottom, top, bottom, bottomLimit);
#ifdef __LP64__
// If the new bottom is equal to the old bottom (when both are treated as floats), we just return the original
// bottom. This prevents rounding errors that can occur when converting newBottom to a double.
if (fabs(static_cast<float>(bottom) - newBottom) <= numeric_limits<float>::epsilon())
return bottom;
else
#endif
return newBottom;
}
@end
@implementation NSView (WebHTMLViewFileInternal)
- (void)_web_addDescendantWebHTMLViewsToArray:(NSMutableArray *)array
{
unsigned count = [_subviews count];
for (unsigned i = 0; i < count; ++i) {
NSView *child = [_subviews objectAtIndex:i];
if ([child isKindOfClass:[WebHTMLView class]])
[array addObject:child];
[child _web_addDescendantWebHTMLViewsToArray:array];
}
}
@end
@implementation NSMutableDictionary (WebHTMLViewFileInternal)
- (void)_web_setObjectIfNotNil:(id)object forKey:(id)key
{
if (object == nil) {
[self removeObjectForKey:key];
} else {
[self setObject:object forKey:key];
}
}
@end
@implementation WebHTMLView
+ (void)initialize
{
[NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes]
returnTypes:[[self class] _insertablePasteboardTypes]];
JSC::initializeThreading();
WTF::initializeMainThreadToProcessMainThread();
WebCore::RunLoop::initializeMainRunLoop();
WebCoreObjCFinalizeOnMainThread(self);
}
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (!self)
return nil;
[self setFocusRingType:NSFocusRingTypeNone];
// Make all drawing go through us instead of subviews.
[self _setDrawsOwnDescendants:YES];
_private = [[WebHTMLViewPrivate alloc] init];
_private->pluginController = [[WebPluginController alloc] initWithDocumentView:self];
return self;
}
- (void)dealloc
{
if (WebCoreObjCScheduleDeallocateOnMainThread([WebHTMLView class], self))
return;
// We can't assert that close has already been called because
// this view can be removed from it's superview, even though
// it could be needed later, so close if needed.
[self close];
[_private release];
_private = nil;
[super dealloc];
}
- (void)finalize
{
ASSERT_MAIN_THREAD();
// We can't assert that close has already been called because
// this view can be removed from it's superview, even though
// it could be needed later, so close if needed.
[self close];
[super finalize];
}
// Returns YES if the delegate returns YES (so we should do no more work).
- (BOOL)callDelegateDoCommandBySelectorIfNeeded:(SEL)selector
{
BOOL callerAlreadyCalledDelegate = _private->selectorForDoCommandBySelector == selector;
_private->selectorForDoCommandBySelector = 0;
if (callerAlreadyCalledDelegate)
return NO;
WebView *webView = [self _webView];
return [[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector];
}
typedef HashMap<SEL, String> SelectorNameMap;
// Map selectors into Editor command names.
// This is not needed for any selectors that have the same name as the Editor command.
static const SelectorNameMap* createSelectorExceptionMap()
{
SelectorNameMap* map = new HashMap<SEL, String>;
map->add(@selector(insertNewlineIgnoringFieldEditor:), "InsertNewline");
map->add(@selector(insertParagraphSeparator:), "InsertNewline");
map->add(@selector(insertTabIgnoringFieldEditor:), "InsertTab");
map->add(@selector(pageDown:), "MovePageDown");
map->add(@selector(pageDownAndModifySelection:), "MovePageDownAndModifySelection");
map->add(@selector(pageUp:), "MovePageUp");
map->add(@selector(pageUpAndModifySelection:), "MovePageUpAndModifySelection");
return map;
}
static String commandNameForSelector(SEL selector)
{
// Check the exception map first.
static const SelectorNameMap* exceptionMap = createSelectorExceptionMap();
SelectorNameMap::const_iterator it = exceptionMap->find(selector);
if (it != exceptionMap->end())
return it->value;
// Remove the trailing colon.
// No need to capitalize the command name since Editor command names are
// not case sensitive.
const char* selectorName = sel_getName(selector);
size_t selectorNameLength = strlen(selectorName);
if (selectorNameLength < 2 || selectorName[selectorNameLength - 1] != ':')
return String();
return String(selectorName, selectorNameLength - 1);
}
- (Editor::Command)coreCommandBySelector:(SEL)selector
{
Frame* coreFrame = core([self _frame]);
if (!coreFrame)
return Editor::Command();
return coreFrame->editor().command(commandNameForSelector(selector));
}
- (Editor::Command)coreCommandByName:(const char*)name
{
Frame* coreFrame = core([self _frame]);
if (!coreFrame)
return Editor::Command();
return coreFrame->editor().command(name);
}
- (void)executeCoreCommandBySelector:(SEL)selector
{
if ([self callDelegateDoCommandBySelectorIfNeeded:selector])
return;
[self coreCommandBySelector:selector].execute();
}
- (void)executeCoreCommandByName:(const char*)name
{
[self coreCommandByName:name].execute();
}
// These commands are forwarded to the Editor object in WebCore.
// Ideally we'd do this for all editing commands; more of the code
// should be moved from here to there, and more commands should be
// added to this list.
// FIXME: Maybe we should set things up so that all these share a single method implementation function.
// The functions are identical.
#define WEBCORE_COMMAND(command) - (void)command:(id)sender { [self executeCoreCommandBySelector:_cmd]; }
WEBCORE_COMMAND(alignCenter)
WEBCORE_COMMAND(alignJustified)
WEBCORE_COMMAND(alignLeft)
WEBCORE_COMMAND(alignRight)
WEBCORE_COMMAND(copy)
WEBCORE_COMMAND(cut)
WEBCORE_COMMAND(paste)
WEBCORE_COMMAND(delete)
WEBCORE_COMMAND(deleteBackward)
WEBCORE_COMMAND(deleteBackwardByDecomposingPreviousCharacter)
WEBCORE_COMMAND(deleteForward)
WEBCORE_COMMAND(deleteToBeginningOfLine)
WEBCORE_COMMAND(deleteToBeginningOfParagraph)
WEBCORE_COMMAND(deleteToEndOfLine)
WEBCORE_COMMAND(deleteToEndOfParagraph)
WEBCORE_COMMAND(deleteToMark)
WEBCORE_COMMAND(deleteWordBackward)
WEBCORE_COMMAND(deleteWordForward)
WEBCORE_COMMAND(ignoreSpelling)
WEBCORE_COMMAND(indent)
WEBCORE_COMMAND(insertBacktab)
WEBCORE_COMMAND(insertLineBreak)
WEBCORE_COMMAND(insertNewline)
WEBCORE_COMMAND(insertNewlineIgnoringFieldEditor)
WEBCORE_COMMAND(insertParagraphSeparator)
WEBCORE_COMMAND(insertTab)
WEBCORE_COMMAND(insertTabIgnoringFieldEditor)
WEBCORE_COMMAND(makeTextWritingDirectionLeftToRight)
WEBCORE_COMMAND(makeTextWritingDirectionNatural)
WEBCORE_COMMAND(makeTextWritingDirectionRightToLeft)
WEBCORE_COMMAND(moveBackward)
WEBCORE_COMMAND(moveBackwardAndModifySelection)
WEBCORE_COMMAND(moveDown)
WEBCORE_COMMAND(moveDownAndModifySelection)
WEBCORE_COMMAND(moveForward)
WEBCORE_COMMAND(moveForwardAndModifySelection)
WEBCORE_COMMAND(moveLeft)
WEBCORE_COMMAND(moveLeftAndModifySelection)
WEBCORE_COMMAND(moveParagraphBackwardAndModifySelection)
WEBCORE_COMMAND(moveParagraphForwardAndModifySelection)
WEBCORE_COMMAND(moveRight)
WEBCORE_COMMAND(moveRightAndModifySelection)
WEBCORE_COMMAND(moveToBeginningOfDocument)
WEBCORE_COMMAND(moveToBeginningOfDocumentAndModifySelection)
WEBCORE_COMMAND(moveToBeginningOfLine)
WEBCORE_COMMAND(moveToBeginningOfLineAndModifySelection)
WEBCORE_COMMAND(moveToBeginningOfParagraph)
WEBCORE_COMMAND(moveToBeginningOfParagraphAndModifySelection)
WEBCORE_COMMAND(moveToBeginningOfSentence)
WEBCORE_COMMAND(moveToBeginningOfSentenceAndModifySelection)
WEBCORE_COMMAND(moveToEndOfDocument)
WEBCORE_COMMAND(moveToEndOfDocumentAndModifySelection)
WEBCORE_COMMAND(moveToEndOfLine)
WEBCORE_COMMAND(moveToEndOfLineAndModifySelection)
WEBCORE_COMMAND(moveToEndOfParagraph)
WEBCORE_COMMAND(moveToEndOfParagraphAndModifySelection)
WEBCORE_COMMAND(moveToEndOfSentence)
WEBCORE_COMMAND(moveToEndOfSentenceAndModifySelection)
WEBCORE_COMMAND(moveToLeftEndOfLine)
WEBCORE_COMMAND(moveToLeftEndOfLineAndModifySelection)
WEBCORE_COMMAND(moveToRightEndOfLine)
WEBCORE_COMMAND(moveToRightEndOfLineAndModifySelection)
WEBCORE_COMMAND(moveUp)
WEBCORE_COMMAND(moveUpAndModifySelection)
WEBCORE_COMMAND(moveWordBackward)
WEBCORE_COMMAND(moveWordBackwardAndModifySelection)
WEBCORE_COMMAND(moveWordForward)
WEBCORE_COMMAND(moveWordForwardAndModifySelection)
WEBCORE_COMMAND(moveWordLeft)
WEBCORE_COMMAND(moveWordLeftAndModifySelection)
WEBCORE_COMMAND(moveWordRight)
WEBCORE_COMMAND(moveWordRightAndModifySelection)
WEBCORE_COMMAND(outdent)
WEBCORE_COMMAND(overWrite)
WEBCORE_COMMAND(pageDown)
WEBCORE_COMMAND(pageDownAndModifySelection)
WEBCORE_COMMAND(pageUp)
WEBCORE_COMMAND(pageUpAndModifySelection)
WEBCORE_COMMAND(pasteAsPlainText)
WEBCORE_COMMAND(selectAll)
WEBCORE_COMMAND(selectLine)
WEBCORE_COMMAND(selectParagraph)
WEBCORE_COMMAND(selectSentence)
WEBCORE_COMMAND(selectToMark)
WEBCORE_COMMAND(selectWord)
WEBCORE_COMMAND(setMark)
WEBCORE_COMMAND(subscript)
WEBCORE_COMMAND(superscript)
WEBCORE_COMMAND(swapWithMark)
WEBCORE_COMMAND(transpose)
WEBCORE_COMMAND(underline)
WEBCORE_COMMAND(unscript)
WEBCORE_COMMAND(yank)
WEBCORE_COMMAND(yankAndSelect)
#undef WEBCORE_COMMAND
#define COMMAND_PROLOGUE if ([self callDelegateDoCommandBySelectorIfNeeded:_cmd]) return;
- (IBAction)takeFindStringFromSelection:(id)sender
{
COMMAND_PROLOGUE
if (![self _hasSelection]) {
NSBeep();
return;
}
[NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self];
}
// This method is needed to support Mac OS X services.
- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types
{
[pasteboard declareTypes:types owner:[self _topHTMLView]];
[self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard];
return YES;
}
- (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
{
BOOL isSendTypeOK = !sendType || ([[self pasteboardTypesForSelection] containsObject:sendType] && [self _hasSelection]);
BOOL isReturnTypeOK = NO;
if (!returnType)
isReturnTypeOK = YES;
else if ([[[self class] _insertablePasteboardTypes] containsObject:returnType] && [self _isEditable]) {
// We can insert strings in any editable context. We can insert other types, like images, only in rich edit contexts.
isReturnTypeOK = [returnType isEqualToString:NSStringPboardType] || [self _canEditRichly];
}
if (isSendTypeOK && isReturnTypeOK)
return self;
return [[self nextResponder] validRequestorForSendType:sendType returnType:returnType];
}
// jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari
// was using the old jumpToSelection selector in its menu. Newer versions of Safari will use the
// selector centerSelectionInVisibleArea. We'll leave the old selector in place for two reasons:
// (1) Compatibility between older Safari and newer WebKit; (2) other WebKit-based applications
// might be using the selector, and we don't want to break them.
- (void)jumpToSelection:(id)sender
{
COMMAND_PROLOGUE
if (Frame* coreFrame = core([self _frame]))
coreFrame->selection()->revealSelection(ScrollAlignment::alignCenterAlways);
}
- (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item
{
SEL action = [item action];
RefPtr<Frame> frame = core([self _frame]);
if (!frame)
return NO;
if (Document* doc = frame->document()) {
if (doc->isPluginDocument())
return NO;
if (doc->isImageDocument()) {
if (action == @selector(copy:))
return frame->loader()->isComplete();
return NO;
}
}
if (action == @selector(changeSpelling:)
|| action == @selector(_changeSpellingFromMenu:)
|| action == @selector(checkSpelling:)
|| action == @selector(complete:)
|| action == @selector(pasteFont:))
return [self _canEdit];
if (action == @selector(showGuessPanel:)) {
// Match OS X AppKit behavior for post-Tiger. Don't change Tiger behavior.
NSMenuItem *menuItem = (NSMenuItem *)item;
if ([menuItem isKindOfClass:[NSMenuItem class]]) {
BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
[menuItem setTitle:panelShowing
? UI_STRING_INTERNAL("Hide Spelling and Grammar", "menu item title")
: UI_STRING_INTERNAL("Show Spelling and Grammar", "menu item title")];
}
return [self _canEdit];
}
if (action == @selector(changeBaseWritingDirection:)
|| action == @selector(makeBaseWritingDirectionLeftToRight:)
|| action == @selector(makeBaseWritingDirectionRightToLeft:)) {
NSWritingDirection writingDirection;
if (action == @selector(changeBaseWritingDirection:)) {
writingDirection = static_cast<NSWritingDirection>([item tag]);
if (writingDirection == NSWritingDirectionNatural)
return NO;
} else if (action == @selector(makeBaseWritingDirectionLeftToRight:))
writingDirection = NSWritingDirectionLeftToRight;
else
writingDirection = NSWritingDirectionRightToLeft;
NSMenuItem *menuItem = (NSMenuItem *)item;
if ([menuItem isKindOfClass:[NSMenuItem class]]) {
String direction = writingDirection == NSWritingDirectionLeftToRight ? "ltr" : "rtl";
[menuItem setState:frame->editor().selectionHasStyle(CSSPropertyDirection, direction)];
}
return [self _canEdit];
}
if (action == @selector(makeBaseWritingDirectionNatural:)) {
NSMenuItem *menuItem = (NSMenuItem *)item;
if ([menuItem isKindOfClass:[NSMenuItem class]])
[menuItem setState:NSOffState];
return NO;
}
if (action == @selector(toggleBaseWritingDirection:)) {
NSMenuItem *menuItem = (NSMenuItem *)item;
if ([menuItem isKindOfClass:[NSMenuItem class]]) {
// Take control of the title of the menu item instead of just checking/unchecking it because
// a check would be ambiguous.
[menuItem setTitle:frame->editor().selectionHasStyle(CSSPropertyDirection, "rtl")
? UI_STRING_INTERNAL("Left to Right", "Left to Right context menu item")
: UI_STRING_INTERNAL("Right to Left", "Right to Left context menu item")];
}
return [self _canEdit];
}
if (action == @selector(changeAttributes:)
|| action == @selector(changeColor:)
|| action == @selector(changeFont:))
return [self _canEditRichly];
if (action == @selector(capitalizeWord:)
|| action == @selector(lowercaseWord:)
|| action == @selector(uppercaseWord:))
return [self _hasSelection] && [self _isEditable];
if (action == @selector(centerSelectionInVisibleArea:)
|| action == @selector(jumpToSelection:)
|| action == @selector(copyFont:))
return [self _hasSelection] || ([self _isEditable] && [self _hasInsertionPoint]);
if (action == @selector(changeDocumentBackgroundColor:))
return [[self _webView] isEditable] && [self _canEditRichly];
if (action == @selector(_ignoreSpellingFromMenu:)
|| action == @selector(_learnSpellingFromMenu:)
|| action == @selector(takeFindStringFromSelection:))
return [self _hasSelection];
if (action == @selector(paste:) || action == @selector(pasteAsPlainText:))
return frame && (frame->editor().canDHTMLPaste() || frame->editor().canPaste());
if (action == @selector(pasteAsRichText:))
return frame && (frame->