diff --git a/widget/src/cocoa/nsChildView.mm b/widget/src/cocoa/nsChildView.mm index 48162343f49..8dceef6703b 100644 --- a/widget/src/cocoa/nsChildView.mm +++ b/widget/src/cocoa/nsChildView.mm @@ -96,6 +96,8 @@ extern "C" { CG_EXTERN void CGContextResetClip(CGContextRef); } +extern PRBool gCocoaWindowMethodsSwizzled; // Defined in nsCocoaWindow.mm + extern nsISupportsArray *gDraggedTransferables; PRBool nsTSMManager::sIsIMEEnabled = PR_TRUE; @@ -391,6 +393,13 @@ nsresult nsChildView::StandardCreate(nsIWidget *aParent, nsWidgetInitData *aInitData, nsNativeWidget aNativeParent) { + // See NSWindow (MethodSwizzling) in nsCocoaWindow.mm. + if (!gCocoaWindowMethodsSwizzled) { + nsToolkit::SwizzleMethods([NSWindow class], @selector(sendEvent:), + @selector(nsCocoaWindow_NSWindow_sendEvent:)); + gCocoaWindowMethodsSwizzled = PR_TRUE; + } + mBounds = aRect; BaseCreate(aParent, aRect, aHandleEventFunction, diff --git a/widget/src/cocoa/nsCocoaWindow.mm b/widget/src/cocoa/nsCocoaWindow.mm index 0bdc5f90ddb..ebbee8db47e 100644 --- a/widget/src/cocoa/nsCocoaWindow.mm +++ b/widget/src/cocoa/nsCocoaWindow.mm @@ -54,9 +54,12 @@ #include "nsIPrefBranch.h" #include "nsToolkit.h" #include "nsPrintfCString.h" +#include "nsThreadUtils.h" PRInt32 gXULModalLevel = 0; +PRBool gCocoaWindowMethodsSwizzled = PR_FALSE; + // defined in nsMenuBarX.mm extern NSMenu* sApplicationMenu; // Application menu shared by all menubars @@ -1750,6 +1753,42 @@ void patternDraw(void* aInfo, CGContextRef aContext) @end +@interface NSWindow (MethodSwizzling) +- (void)nsCocoaWindow_NSWindow_sendEvent:(NSEvent *)anEvent; +@end + +@implementation NSWindow (MethodSwizzling) + +// A mouseDown event can change the focus, and (if this happens) we may need +// to send NS_LOSTFOCUS and NS_GOTFOCUS events to Gecko. Otherwise other +// Gecko events may be sent to the wrong nsChildView, or the "right" +// nsChildView may not be focused. This resolves bmo bug 413882. +// For non-embedders (e.g. Firefox and Seamonkey), it would probably only be +// necessary to add a sendEvent: method to the ToolbarWindow class. But +// embedders (like Camino) generally create their own NSWindows. So in order +// to fix this problem everywhere, it's necessary to "hook" NSWindow's own +// sendEvent: method. +- (void)nsCocoaWindow_NSWindow_sendEvent:(NSEvent *)anEvent +{ + NSResponder *oldFirstResponder = nil; + NSEventType type = [anEvent type]; + if (type == NSLeftMouseDown) + oldFirstResponder = [[self firstResponder] retain]; + [self nsCocoaWindow_NSWindow_sendEvent:anEvent]; + if (type == NSLeftMouseDown) { + NSResponder *newFirstResponder = [self firstResponder]; + if (oldFirstResponder != newFirstResponder) { + if ([oldFirstResponder isKindOfClass:[ChildView class]]) + [(ChildView *)oldFirstResponder sendFocusEvent:NS_LOSTFOCUS]; + if ([newFirstResponder isKindOfClass:[ChildView class]]) + [(ChildView *)newFirstResponder sendFocusEvent:NS_GOTFOCUS]; + } + [oldFirstResponder release]; + } +} + +@end + @implementation PopupWindow // The OS treats our custom popup windows very strangely -- many mouse events diff --git a/widget/src/cocoa/nsToolkit.h b/widget/src/cocoa/nsToolkit.h index ee1bf062c98..fceb83aec25 100644 --- a/widget/src/cocoa/nsToolkit.h +++ b/widget/src/cocoa/nsToolkit.h @@ -42,6 +42,8 @@ #include "nsIToolkit.h" #import +#import +#import #import #define MAC_OS_X_VERSION_10_4_HEX 0x00001040 @@ -64,6 +66,8 @@ public: static void PostSleepWakeNotification(const char* aNotification); + static nsresult SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod); + protected: nsresult RegisterForSleepWakeNotifcations(); diff --git a/widget/src/cocoa/nsToolkit.mm b/widget/src/cocoa/nsToolkit.mm index d1831d92f47..e794363fffe 100644 --- a/widget/src/cocoa/nsToolkit.mm +++ b/widget/src/cocoa/nsToolkit.mm @@ -382,3 +382,37 @@ PRBool nsToolkit::OnLeopardOrLater() { return (OSXVersion() >= MAC_OS_X_VERSION_10_5_HEX) ? PR_TRUE : PR_FALSE; } + + +// An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X +// Leopard and is available to 64-bit binaries on Leopard and above. Based on +// ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling. +// Since the Method type becomes an opaque type as of Objective-C 2.0, we'll +// have to switch to using accessor methods like method_exchangeImplementations() +// when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard +// and above). But these accessor methods aren't available in Objective-C 1 +// (or on Tiger). So we need to access Method's members directly for (Tiger- +// capable) binaries (32-bit or 64-bit) that use Objective-C 1 (as long as we +// keep supporting Tiger). +// +// Be aware that, if aClass doesn't have an orgMethod selector but one of its +// superclasses does, the method substitution will (in effect) take place in +// that superclass (rather than in aClass itself). The substitution has +// effect on the class where it takes place and all of that class's +// subclasses. In order for method swizzling to work properly, posedMethod +// needs to be unique in the class where the substitution takes place and all +// of its subclasses. +nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod) +{ + Method original = class_getInstanceMethod(aClass, orgMethod); + Method posed = class_getInstanceMethod(aClass, posedMethod); + + if (!original || !posed) + return NS_ERROR_FAILURE; + + IMP aMethodImp = original->method_imp; + original->method_imp = posed->method_imp; + posed->method_imp = aMethodImp; + + return NS_OK; +}