/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ // vim:set ts=2 sts=2 sw=2 et cin: // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * 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. // * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT // OWNER OR 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. #include "base/basictypes.h" #include "nsCocoaUtils.h" #include "PluginModuleChild.h" #include "nsDebug.h" #include "PluginInterposeOSX.h" #include <set> #import <AppKit/AppKit.h> #import <objc/runtime.h> #import <Carbon/Carbon.h> using namespace mozilla::plugins; namespace mac_plugin_interposing { int32_t NSCursorInfo::mNativeCursorsSupported = -1; // This constructor may be called from the browser process or the plugin // process. NSCursorInfo::NSCursorInfo() : mType(TypeArrow) , mHotSpot(nsPoint(0, 0)) , mCustomImageData(NULL) , mCustomImageDataLength(0) { } NSCursorInfo::NSCursorInfo(NSCursor* aCursor) : mType(TypeArrow) , mHotSpot(nsPoint(0, 0)) , mCustomImageData(NULL) , mCustomImageDataLength(0) { // This constructor is only ever called from the plugin process, so the // following is safe. if (!GetNativeCursorsSupported()) { return; } NSPoint hotSpotCocoa = [aCursor hotSpot]; mHotSpot = nsPoint(hotSpotCocoa.x, hotSpotCocoa.y); Class nsCursorClass = [NSCursor class]; if ([aCursor isEqual:[NSCursor arrowCursor]]) { mType = TypeArrow; } else if ([aCursor isEqual:[NSCursor closedHandCursor]]) { mType = TypeClosedHand; } else if ([aCursor isEqual:[NSCursor crosshairCursor]]) { mType = TypeCrosshair; } else if ([aCursor isEqual:[NSCursor disappearingItemCursor]]) { mType = TypeDisappearingItem; } else if ([aCursor isEqual:[NSCursor IBeamCursor]]) { mType = TypeIBeam; } else if ([aCursor isEqual:[NSCursor openHandCursor]]) { mType = TypeOpenHand; } else if ([aCursor isEqual:[NSCursor pointingHandCursor]]) { mType = TypePointingHand; } else if ([aCursor isEqual:[NSCursor resizeDownCursor]]) { mType = TypeResizeDown; } else if ([aCursor isEqual:[NSCursor resizeLeftCursor]]) { mType = TypeResizeLeft; } else if ([aCursor isEqual:[NSCursor resizeLeftRightCursor]]) { mType = TypeResizeLeftRight; } else if ([aCursor isEqual:[NSCursor resizeRightCursor]]) { mType = TypeResizeRight; } else if ([aCursor isEqual:[NSCursor resizeUpCursor]]) { mType = TypeResizeUp; } else if ([aCursor isEqual:[NSCursor resizeUpDownCursor]]) { mType = TypeResizeUpDown; // The following cursor types are only supported on OS X 10.6 and up. } else if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)] && [aCursor isEqual:[nsCursorClass performSelector:@selector(contextualMenuCursor)]]) { mType = TypeContextualMenu; } else if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)] && [aCursor isEqual:[nsCursorClass performSelector:@selector(dragCopyCursor)]]) { mType = TypeDragCopy; } else if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)] && [aCursor isEqual:[nsCursorClass performSelector:@selector(dragLinkCursor)]]) { mType = TypeDragLink; } else if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)] && [aCursor isEqual:[nsCursorClass performSelector:@selector(operationNotAllowedCursor)]]) { mType = TypeNotAllowed; } else { NSImage* image = [aCursor image]; NSArray* reps = image ? [image representations] : nil; NSUInteger repsCount = reps ? [reps count] : 0; if (!repsCount) { // If we have a custom cursor with no image representations, assume we // need a transparent cursor. mType = TypeTransparent; } else { CGImageRef cgImage = nil; // XXX We don't know how to deal with a cursor that doesn't have a // bitmap image representation. For now we fall back to an arrow // cursor. for (NSUInteger i = 0; i < repsCount; ++i) { id rep = [reps objectAtIndex:i]; if ([rep isKindOfClass:[NSBitmapImageRep class]]) { cgImage = [(NSBitmapImageRep*)rep CGImage]; break; } } if (cgImage) { CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); if (data) { CGImageDestinationRef dest = ::CGImageDestinationCreateWithData(data, kUTTypePNG, 1, NULL); if (dest) { ::CGImageDestinationAddImage(dest, cgImage, NULL); if (::CGImageDestinationFinalize(dest)) { uint32_t dataLength = (uint32_t) ::CFDataGetLength(data); mCustomImageData = (uint8_t*) moz_xmalloc(dataLength); ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData); mCustomImageDataLength = dataLength; mType = TypeCustom; } ::CFRelease(dest); } ::CFRelease(data); } } if (!mCustomImageData) { mType = TypeArrow; } } } } NSCursorInfo::NSCursorInfo(const Cursor* aCursor) : mType(TypeArrow) , mHotSpot(nsPoint(0, 0)) , mCustomImageData(NULL) , mCustomImageDataLength(0) { // This constructor is only ever called from the plugin process, so the // following is safe. if (!GetNativeCursorsSupported()) { return; } mHotSpot = nsPoint(aCursor->hotSpot.h, aCursor->hotSpot.v); int width = 16, height = 16; int bytesPerPixel = 4; int rowBytes = width * bytesPerPixel; int bitmapSize = height * rowBytes; bool isTransparent = true; uint8_t* bitmap = (uint8_t*) moz_xmalloc(bitmapSize); // The way we create 'bitmap' is largely "borrowed" from Chrome's // WebCursor::InitFromCursor(). for (int y = 0; y < height; ++y) { unsigned short data = aCursor->data[y]; unsigned short mask = aCursor->mask[y]; // Change 'data' and 'mask' from big-endian to little-endian, but output // big-endian data below. data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF); mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF); // It'd be nice to use a gray-scale bitmap. But // CGBitmapContextCreateImage() (used below) won't work with one that also // has alpha values. for (int x = 0; x < width; ++x) { int offset = (y * rowBytes) + (x * bytesPerPixel); // Color value if (data & 0x8000) { bitmap[offset] = 0x0; bitmap[offset + 1] = 0x0; bitmap[offset + 2] = 0x0; } else { bitmap[offset] = 0xFF; bitmap[offset + 1] = 0xFF; bitmap[offset + 2] = 0xFF; } // Mask value if (mask & 0x8000) { bitmap[offset + 3] = 0xFF; isTransparent = false; } else { bitmap[offset + 3] = 0x0; } data <<= 1; mask <<= 1; } } if (isTransparent) { // If aCursor is transparent, we don't need to serialize custom cursor // data over IPC. mType = TypeTransparent; } else { CGColorSpaceRef color = ::CGColorSpaceCreateDeviceRGB(); if (color) { CGContextRef context = ::CGBitmapContextCreate(bitmap, width, height, 8, rowBytes, color, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); if (context) { CGImageRef image = ::CGBitmapContextCreateImage(context); if (image) { ::CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); if (data) { CGImageDestinationRef dest = ::CGImageDestinationCreateWithData(data, kUTTypePNG, 1, NULL); if (dest) { ::CGImageDestinationAddImage(dest, image, NULL); if (::CGImageDestinationFinalize(dest)) { uint32_t dataLength = (uint32_t) ::CFDataGetLength(data); mCustomImageData = (uint8_t*) moz_xmalloc(dataLength); ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData); mCustomImageDataLength = dataLength; mType = TypeCustom; } ::CFRelease(dest); } ::CFRelease(data); } ::CGImageRelease(image); } ::CGContextRelease(context); } ::CGColorSpaceRelease(color); } } free(bitmap); } NSCursorInfo::~NSCursorInfo() { if (mCustomImageData) { free(mCustomImageData); } } NSCursor* NSCursorInfo::GetNSCursor() const { NSCursor* retval = nil; Class nsCursorClass = [NSCursor class]; switch(mType) { case TypeArrow: retval = [NSCursor arrowCursor]; break; case TypeClosedHand: retval = [NSCursor closedHandCursor]; break; case TypeCrosshair: retval = [NSCursor crosshairCursor]; break; case TypeDisappearingItem: retval = [NSCursor disappearingItemCursor]; break; case TypeIBeam: retval = [NSCursor IBeamCursor]; break; case TypeOpenHand: retval = [NSCursor openHandCursor]; break; case TypePointingHand: retval = [NSCursor pointingHandCursor]; break; case TypeResizeDown: retval = [NSCursor resizeDownCursor]; break; case TypeResizeLeft: retval = [NSCursor resizeLeftCursor]; break; case TypeResizeLeftRight: retval = [NSCursor resizeLeftRightCursor]; break; case TypeResizeRight: retval = [NSCursor resizeRightCursor]; break; case TypeResizeUp: retval = [NSCursor resizeUpCursor]; break; case TypeResizeUpDown: retval = [NSCursor resizeUpDownCursor]; break; // The following four cursor types are only supported on OS X 10.6 and up. case TypeContextualMenu: { if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)]) { retval = [nsCursorClass performSelector:@selector(contextualMenuCursor)]; } break; } case TypeDragCopy: { if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)]) { retval = [nsCursorClass performSelector:@selector(dragCopyCursor)]; } break; } case TypeDragLink: { if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)]) { retval = [nsCursorClass performSelector:@selector(dragLinkCursor)]; } break; } case TypeNotAllowed: { if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)]) { retval = [nsCursorClass performSelector:@selector(operationNotAllowedCursor)]; } break; } case TypeTransparent: retval = GetTransparentCursor(); break; default: break; } if (!retval && mCustomImageData && mCustomImageDataLength) { CGDataProviderRef provider = ::CGDataProviderCreateWithData(NULL, (const void*)mCustomImageData, mCustomImageDataLength, NULL); if (provider) { CGImageRef cgImage = ::CGImageCreateWithPNGDataProvider(provider, NULL, false, kCGRenderingIntentDefault); if (cgImage) { NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; if (rep) { NSImage* image = [[NSImage alloc] init]; if (image) { [image addRepresentation:rep]; retval = [[[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] autorelease]; [image release]; } [rep release]; } ::CGImageRelease(cgImage); } ::CFRelease(provider); } } // Fall back to an arrow cursor if need be. if (!retval) { retval = [NSCursor arrowCursor]; } return retval; } // Get a transparent cursor with the appropriate hot spot. We need one if // (for example) we have a custom cursor with no image data. NSCursor* NSCursorInfo::GetTransparentCursor() const { NSCursor* retval = nil; int width = 16, height = 16; int bytesPerPixel = 2; int rowBytes = width * bytesPerPixel; int dataSize = height * rowBytes; uint8_t* data = (uint8_t*) moz_xmalloc(dataSize); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { int offset = (y * rowBytes) + (x * bytesPerPixel); data[offset] = 0x7E; // Arbitrary gray-scale value data[offset + 1] = 0; // Alpha value to make us transparent } } NSBitmapImageRep* imageRep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil pixelsWide:width pixelsHigh:height bitsPerSample:8 samplesPerPixel:2 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedWhiteColorSpace bytesPerRow:rowBytes bitsPerPixel:16] autorelease]; if (imageRep) { uint8_t* repDataPtr = [imageRep bitmapData]; if (repDataPtr) { memcpy(repDataPtr, data, dataSize); NSImage *image = [[[NSImage alloc] initWithSize:NSMakeSize(width, height)] autorelease]; if (image) { [image addRepresentation:imageRep]; retval = [[[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] autorelease]; } } } free(data); // Fall back to an arrow cursor if (for some reason) the above code failed. if (!retval) { retval = [NSCursor arrowCursor]; } return retval; } NSCursorInfo::Type NSCursorInfo::GetType() const { return mType; } const char* NSCursorInfo::GetTypeName() const { switch(mType) { case TypeCustom: return "TypeCustom"; case TypeArrow: return "TypeArrow"; case TypeClosedHand: return "TypeClosedHand"; case TypeContextualMenu: return "TypeContextualMenu"; case TypeCrosshair: return "TypeCrosshair"; case TypeDisappearingItem: return "TypeDisappearingItem"; case TypeDragCopy: return "TypeDragCopy"; case TypeDragLink: return "TypeDragLink"; case TypeIBeam: return "TypeIBeam"; case TypeNotAllowed: return "TypeNotAllowed"; case TypeOpenHand: return "TypeOpenHand"; case TypePointingHand: return "TypePointingHand"; case TypeResizeDown: return "TypeResizeDown"; case TypeResizeLeft: return "TypeResizeLeft"; case TypeResizeLeftRight: return "TypeResizeLeftRight"; case TypeResizeRight: return "TypeResizeRight"; case TypeResizeUp: return "TypeResizeUp"; case TypeResizeUpDown: return "TypeResizeUpDown"; case TypeTransparent: return "TypeTransparent"; default: break; } return "TypeUnknown"; } nsPoint NSCursorInfo::GetHotSpot() const { return mHotSpot; } uint8_t* NSCursorInfo::GetCustomImageData() const { return mCustomImageData; } uint32_t NSCursorInfo::GetCustomImageDataLength() const { return mCustomImageDataLength; } void NSCursorInfo::SetType(Type aType) { mType = aType; } void NSCursorInfo::SetHotSpot(nsPoint aHotSpot) { mHotSpot = aHotSpot; } void NSCursorInfo::SetCustomImageData(uint8_t* aData, uint32_t aDataLength) { if (mCustomImageData) { free(mCustomImageData); } if (aDataLength) { mCustomImageData = (uint8_t*) moz_xmalloc(aDataLength); memcpy(mCustomImageData, aData, aDataLength); } else { mCustomImageData = NULL; } mCustomImageDataLength = aDataLength; } // This should never be called from the browser process -- only from the // plugin process. bool NSCursorInfo::GetNativeCursorsSupported() { if (mNativeCursorsSupported == -1) { ENSURE_PLUGIN_THREAD(false); PluginModuleChild *pmc = PluginModuleChild::GetChrome(); if (pmc) { bool result = pmc->GetNativeCursorsSupported(); if (result) { mNativeCursorsSupported = 1; } else { mNativeCursorsSupported = 0; } } } return (mNativeCursorsSupported == 1); } } // namespace mac_plugin_interposing namespace mac_plugin_interposing { namespace parent { // Tracks plugin windows currently visible. std::set<uint32_t> plugin_visible_windows_set_; // Tracks full screen windows currently visible. std::set<uint32_t> plugin_fullscreen_windows_set_; // Tracks modal windows currently visible. std::set<uint32_t> plugin_modal_windows_set_; void OnPluginShowWindow(uint32_t window_id, CGRect window_bounds, bool modal) { plugin_visible_windows_set_.insert(window_id); if (modal) plugin_modal_windows_set_.insert(window_id); CGRect main_display_bounds = ::CGDisplayBounds(CGMainDisplayID()); if (CGRectEqualToRect(window_bounds, main_display_bounds) && (plugin_fullscreen_windows_set_.find(window_id) == plugin_fullscreen_windows_set_.end())) { plugin_fullscreen_windows_set_.insert(window_id); nsCocoaUtils::HideOSChromeOnScreen(true); } } static void ActivateProcess(pid_t pid) { ProcessSerialNumber process; OSStatus status = ::GetProcessForPID(pid, &process); if (status == noErr) { SetFrontProcess(&process); } else { NS_WARNING("Unable to get process for pid."); } } // Must be called on the UI thread. // If plugin_pid is -1, the browser will be the active process on return, // otherwise that process will be given focus back before this function returns. static void ReleasePluginFullScreen(pid_t plugin_pid) { // Releasing full screen only works if we are the frontmost process; grab // focus, but give it back to the plugin process if requested. ActivateProcess(base::GetCurrentProcId()); nsCocoaUtils::HideOSChromeOnScreen(false); if (plugin_pid != -1) { ActivateProcess(plugin_pid); } } void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid) { bool had_windows = !plugin_visible_windows_set_.empty(); plugin_visible_windows_set_.erase(window_id); bool browser_needs_activation = had_windows && plugin_visible_windows_set_.empty(); plugin_modal_windows_set_.erase(window_id); if (plugin_fullscreen_windows_set_.find(window_id) != plugin_fullscreen_windows_set_.end()) { plugin_fullscreen_windows_set_.erase(window_id); pid_t plugin_pid = browser_needs_activation ? -1 : aPluginPid; browser_needs_activation = false; ReleasePluginFullScreen(plugin_pid); } if (browser_needs_activation) { ActivateProcess(getpid()); } } void OnSetCursor(const NSCursorInfo& cursorInfo) { NSCursor* aCursor = cursorInfo.GetNSCursor(); if (aCursor) { [aCursor set]; } } void OnShowCursor(bool show) { if (show) { [NSCursor unhide]; } else { [NSCursor hide]; } } void OnPushCursor(const NSCursorInfo& cursorInfo) { NSCursor* aCursor = cursorInfo.GetNSCursor(); if (aCursor) { [aCursor push]; } } void OnPopCursor() { [NSCursor pop]; } } // namespace parent } // namespace mac_plugin_interposing namespace mac_plugin_interposing { namespace child { // TODO(stuartmorgan): Make this an IPC to order the plugin process above the // browser process only if the browser is current frontmost. void FocusPluginProcess() { ProcessSerialNumber this_process, front_process; if ((GetCurrentProcess(&this_process) != noErr) || (GetFrontProcess(&front_process) != noErr)) { return; } Boolean matched = false; if ((SameProcess(&this_process, &front_process, &matched) == noErr) && !matched) { SetFrontProcess(&this_process); } } void NotifyBrowserOfPluginShowWindow(uint32_t window_id, CGRect bounds, bool modal) { ENSURE_PLUGIN_THREAD_VOID(); PluginModuleChild *pmc = PluginModuleChild::GetChrome(); if (pmc) pmc->PluginShowWindow(window_id, modal, bounds); } void NotifyBrowserOfPluginHideWindow(uint32_t window_id, CGRect bounds) { ENSURE_PLUGIN_THREAD_VOID(); PluginModuleChild *pmc = PluginModuleChild::GetChrome(); if (pmc) pmc->PluginHideWindow(window_id); } void NotifyBrowserOfSetCursor(NSCursorInfo& aCursorInfo) { ENSURE_PLUGIN_THREAD_VOID(); PluginModuleChild *pmc = PluginModuleChild::GetChrome(); if (pmc) { pmc->SetCursor(aCursorInfo); } } void NotifyBrowserOfShowCursor(bool show) { ENSURE_PLUGIN_THREAD_VOID(); PluginModuleChild *pmc = PluginModuleChild::GetChrome(); if (pmc) { pmc->ShowCursor(show); } } void NotifyBrowserOfPushCursor(NSCursorInfo& aCursorInfo) { ENSURE_PLUGIN_THREAD_VOID(); PluginModuleChild *pmc = PluginModuleChild::GetChrome(); if (pmc) { pmc->PushCursor(aCursorInfo); } } void NotifyBrowserOfPopCursor() { ENSURE_PLUGIN_THREAD_VOID(); PluginModuleChild *pmc = PluginModuleChild::GetChrome(); if (pmc) { pmc->PopCursor(); } } struct WindowInfo { uint32_t window_id; CGRect bounds; explicit WindowInfo(NSWindow* aWindow) { NSInteger window_num = [aWindow windowNumber]; window_id = window_num > 0 ? window_num : 0; bounds = NSRectToCGRect([aWindow frame]); } }; static void OnPluginWindowClosed(const WindowInfo& window_info) { if (window_info.window_id == 0) return; mac_plugin_interposing::child::NotifyBrowserOfPluginHideWindow(window_info.window_id, window_info.bounds); } static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) { // The window id is 0 if it has never been shown (including while it is the // process of being shown for the first time); when that happens, we'll catch // it in _setWindowNumber instead. static BOOL s_pending_display_is_modal = NO; if (window_info.window_id == 0) { if (is_modal) s_pending_display_is_modal = YES; return; } if (s_pending_display_is_modal) { is_modal = YES; s_pending_display_is_modal = NO; } mac_plugin_interposing::child::NotifyBrowserOfPluginShowWindow( window_info.window_id, window_info.bounds, is_modal); } static BOOL OnSetCursor(NSCursorInfo &aInfo) { if (NSCursorInfo::GetNativeCursorsSupported()) { NotifyBrowserOfSetCursor(aInfo); return YES; } return NO; } static BOOL OnHideCursor() { if (NSCursorInfo::GetNativeCursorsSupported()) { NotifyBrowserOfShowCursor(NO); return YES; } return NO; } static BOOL OnUnhideCursor() { if (NSCursorInfo::GetNativeCursorsSupported()) { NotifyBrowserOfShowCursor(YES); return YES; } return NO; } static BOOL OnPushCursor(NSCursorInfo &aInfo) { if (NSCursorInfo::GetNativeCursorsSupported()) { NotifyBrowserOfPushCursor(aInfo); return YES; } return NO; } static BOOL OnPopCursor() { if (NSCursorInfo::GetNativeCursorsSupported()) { NotifyBrowserOfPopCursor(); return YES; } return NO; } } // namespace child } // namespace mac_plugin_interposing using namespace mac_plugin_interposing::child; @interface NSWindow (PluginInterposing) - (void)pluginInterpose_orderOut:(id)sender; - (void)pluginInterpose_orderFront:(id)sender; - (void)pluginInterpose_makeKeyAndOrderFront:(id)sender; - (void)pluginInterpose_setWindowNumber:(NSInteger)num; @end @implementation NSWindow (PluginInterposing) - (void)pluginInterpose_orderOut:(id)sender { WindowInfo window_info(self); [self pluginInterpose_orderOut:sender]; OnPluginWindowClosed(window_info); } - (void)pluginInterpose_orderFront:(id)sender { mac_plugin_interposing::child::FocusPluginProcess(); [self pluginInterpose_orderFront:sender]; OnPluginWindowShown(WindowInfo(self), NO); } - (void)pluginInterpose_makeKeyAndOrderFront:(id)sender { mac_plugin_interposing::child::FocusPluginProcess(); [self pluginInterpose_makeKeyAndOrderFront:sender]; OnPluginWindowShown(WindowInfo(self), NO); } - (void)pluginInterpose_setWindowNumber:(NSInteger)num { if (num > 0) mac_plugin_interposing::child::FocusPluginProcess(); [self pluginInterpose_setWindowNumber:num]; if (num > 0) OnPluginWindowShown(WindowInfo(self), NO); } @end @interface NSApplication (PluginInterposing) - (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window; @end @implementation NSApplication (PluginInterposing) - (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window { mac_plugin_interposing::child::FocusPluginProcess(); // This is out-of-order relative to the other calls, but runModalForWindow: // won't return until the window closes, and the order only matters for // full-screen windows. OnPluginWindowShown(WindowInfo(window), YES); return [self pluginInterpose_runModalForWindow:window]; } @end // Hook commands to manipulate the current cursor, so that they can be passed // from the child process to the parent process. These commands have no // effect unless they're performed in the parent process. @interface NSCursor (PluginInterposing) - (void)pluginInterpose_set; - (void)pluginInterpose_push; - (void)pluginInterpose_pop; + (NSCursor*)pluginInterpose_currentCursor; + (void)pluginInterpose_hide; + (void)pluginInterpose_unhide; + (void)pluginInterpose_pop; @end // Cache the results of [NSCursor set], [NSCursor push] and [NSCursor pop]. // The last element is always the current cursor. static NSMutableArray* gCursorStack = nil; static BOOL initCursorStack() { if (!gCursorStack) { gCursorStack = [[NSMutableArray arrayWithCapacity:5] retain]; } return (gCursorStack != NULL); } static NSCursor* currentCursorFromCache() { if (!initCursorStack()) return nil; return (NSCursor*) [gCursorStack lastObject]; } static void setCursorInCache(NSCursor* aCursor) { if (!initCursorStack() || !aCursor) return; NSUInteger count = [gCursorStack count]; if (count) { [gCursorStack replaceObjectAtIndex:count - 1 withObject:aCursor]; } else { [gCursorStack addObject:aCursor]; } } static void pushCursorInCache(NSCursor* aCursor) { if (!initCursorStack() || !aCursor) return; [gCursorStack addObject:aCursor]; } static void popCursorInCache() { if (!initCursorStack()) return; // Apple's doc on the +[NSCursor pop] method says: "If the current cursor // is the only cursor on the stack, this method does nothing." if ([gCursorStack count] > 1) { [gCursorStack removeLastObject]; } } @implementation NSCursor (PluginInterposing) - (void)pluginInterpose_set { NSCursorInfo info(self); OnSetCursor(info); setCursorInCache(self); [self pluginInterpose_set]; } - (void)pluginInterpose_push { NSCursorInfo info(self); OnPushCursor(info); pushCursorInCache(self); [self pluginInterpose_push]; } - (void)pluginInterpose_pop { OnPopCursor(); popCursorInCache(); [self pluginInterpose_pop]; } // The currentCursor method always returns nil when running in a background // process. But this may confuse plugins (notably Flash, see bug 621117). So // if we get a nil return from the "call to super", we return a cursor that's // been cached by previous calls to set or push. According to Apple's docs, // currentCursor "only returns the cursor set by your application using // NSCursor methods". So we don't need to worry about changes to the cursor // made by other methods like SetThemeCursor(). + (NSCursor*)pluginInterpose_currentCursor { NSCursor* retval = [self pluginInterpose_currentCursor]; if (!retval) { retval = currentCursorFromCache(); } return retval; } + (void)pluginInterpose_hide { OnHideCursor(); [self pluginInterpose_hide]; } + (void)pluginInterpose_unhide { OnUnhideCursor(); [self pluginInterpose_unhide]; } + (void)pluginInterpose_pop { OnPopCursor(); popCursorInCache(); [self pluginInterpose_pop]; } @end static void ExchangeMethods(Class target_class, BOOL class_method, SEL original, SEL replacement) { Method m1; Method m2; if (class_method) { m1 = class_getClassMethod(target_class, original); m2 = class_getClassMethod(target_class, replacement); } else { m1 = class_getInstanceMethod(target_class, original); m2 = class_getInstanceMethod(target_class, replacement); } if (m1 == m2) return; if (m1 && m2) method_exchangeImplementations(m1, m2); else NS_NOTREACHED("Cocoa swizzling failed"); } namespace mac_plugin_interposing { namespace child { void SetUpCocoaInterposing() { Class nswindow_class = [NSWindow class]; ExchangeMethods(nswindow_class, NO, @selector(orderOut:), @selector(pluginInterpose_orderOut:)); ExchangeMethods(nswindow_class, NO, @selector(orderFront:), @selector(pluginInterpose_orderFront:)); ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:), @selector(pluginInterpose_makeKeyAndOrderFront:)); ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:), @selector(pluginInterpose_setWindowNumber:)); ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:), @selector(pluginInterpose_runModalForWindow:)); Class nscursor_class = [NSCursor class]; ExchangeMethods(nscursor_class, NO, @selector(set), @selector(pluginInterpose_set)); ExchangeMethods(nscursor_class, NO, @selector(push), @selector(pluginInterpose_push)); ExchangeMethods(nscursor_class, NO, @selector(pop), @selector(pluginInterpose_pop)); ExchangeMethods(nscursor_class, YES, @selector(currentCursor), @selector(pluginInterpose_currentCursor)); ExchangeMethods(nscursor_class, YES, @selector(hide), @selector(pluginInterpose_hide)); ExchangeMethods(nscursor_class, YES, @selector(unhide), @selector(pluginInterpose_unhide)); ExchangeMethods(nscursor_class, YES, @selector(pop), @selector(pluginInterpose_pop)); } } // namespace child } // namespace mac_plugin_interposing // Called from plugin_child_interpose.mm, which hooks calls to // SetCursor() (the QuickDraw call) from the plugin child process. extern "C" NS_VISIBILITY_DEFAULT BOOL mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor) { NSCursorInfo info(cursor); return OnSetCursor(info); } // Called from plugin_child_interpose.mm, which hooks calls to // SetThemeCursor() (the Appearance Manager call) from the plugin child // process. extern "C" NS_VISIBILITY_DEFAULT BOOL mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor) { NSCursorInfo info; switch (cursor) { case kThemeArrowCursor: info.SetType(NSCursorInfo::TypeArrow); break; case kThemeCopyArrowCursor: info.SetType(NSCursorInfo::TypeDragCopy); break; case kThemeAliasArrowCursor: info.SetType(NSCursorInfo::TypeDragLink); break; case kThemeContextualMenuArrowCursor: info.SetType(NSCursorInfo::TypeContextualMenu); break; case kThemeIBeamCursor: info.SetType(NSCursorInfo::TypeIBeam); break; case kThemeCrossCursor: case kThemePlusCursor: info.SetType(NSCursorInfo::TypeCrosshair); break; case kThemeWatchCursor: case kThemeSpinningCursor: info.SetType(NSCursorInfo::TypeArrow); break; case kThemeClosedHandCursor: info.SetType(NSCursorInfo::TypeClosedHand); break; case kThemeOpenHandCursor: info.SetType(NSCursorInfo::TypeOpenHand); break; case kThemePointingHandCursor: case kThemeCountingUpHandCursor: case kThemeCountingDownHandCursor: case kThemeCountingUpAndDownHandCursor: info.SetType(NSCursorInfo::TypePointingHand); break; case kThemeResizeLeftCursor: info.SetType(NSCursorInfo::TypeResizeLeft); break; case kThemeResizeRightCursor: info.SetType(NSCursorInfo::TypeResizeRight); break; case kThemeResizeLeftRightCursor: info.SetType(NSCursorInfo::TypeResizeLeftRight); break; case kThemeNotAllowedCursor: info.SetType(NSCursorInfo::TypeNotAllowed); break; case kThemeResizeUpCursor: info.SetType(NSCursorInfo::TypeResizeUp); break; case kThemeResizeDownCursor: info.SetType(NSCursorInfo::TypeResizeDown); break; case kThemeResizeUpDownCursor: info.SetType(NSCursorInfo::TypeResizeUpDown); break; case kThemePoofCursor: info.SetType(NSCursorInfo::TypeDisappearingItem); break; default: info.SetType(NSCursorInfo::TypeArrow); break; } return OnSetCursor(info); } extern "C" NS_VISIBILITY_DEFAULT BOOL mac_plugin_interposing_child_OnHideCursor() { return OnHideCursor(); } extern "C" NS_VISIBILITY_DEFAULT BOOL mac_plugin_interposing_child_OnShowCursor() { return OnUnhideCursor(); }