michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: // vim:set ts=2 sts=2 sw=2 et cin: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #import michael@0: #import michael@0: #include "PluginUtilsOSX.h" michael@0: michael@0: // Remove definitions for try/catch interfering with ObjCException macros. michael@0: #include "nsObjCExceptions.h" michael@0: #include "nsCocoaUtils.h" michael@0: michael@0: #include "nsDebug.h" michael@0: michael@0: @interface CALayer (ContentsScale) michael@0: - (double)contentsScale; michael@0: - (void)setContentsScale:(double)scale; michael@0: @end michael@0: michael@0: using namespace mozilla::plugins::PluginUtilsOSX; michael@0: michael@0: @interface CGBridgeLayer : CALayer { michael@0: DrawPluginFunc mDrawFunc; michael@0: void* mPluginInstance; michael@0: nsIntRect mUpdateRect; michael@0: BOOL mAvoidCGCrashes; michael@0: CGContextRef mLastCGContext; michael@0: } michael@0: - (void)setDrawFunc:(DrawPluginFunc)aFunc michael@0: pluginInstance:(void*)aPluginInstance michael@0: avoidCGCrashes:(BOOL)aAvoidCGCrashes; michael@0: - (void)updateRect:(nsIntRect)aRect; michael@0: - (void)protectLastCGContext; michael@0: michael@0: @end michael@0: michael@0: // CGBitmapContextSetData() is an undocumented function present (with michael@0: // the same signature) since at least OS X 10.5. As the name suggests, michael@0: // it's used to replace the "data" in a bitmap context that was michael@0: // originally specified in a call to CGBitmapContextCreate() or michael@0: // CGBitmapContextCreateWithData(). michael@0: typedef void (*CGBitmapContextSetDataFunc) (CGContextRef c, michael@0: size_t x, michael@0: size_t y, michael@0: size_t width, michael@0: size_t height, michael@0: void* data, michael@0: size_t bitsPerComponent, michael@0: size_t bitsPerPixel, michael@0: size_t bytesPerRow); michael@0: CGBitmapContextSetDataFunc CGBitmapContextSetDataPtr = NULL; michael@0: michael@0: @implementation CGBridgeLayer michael@0: - (void) updateRect:(nsIntRect)aRect michael@0: { michael@0: mUpdateRect.UnionRect(mUpdateRect, aRect); michael@0: } michael@0: michael@0: - (void) setDrawFunc:(DrawPluginFunc)aFunc michael@0: pluginInstance:(void*)aPluginInstance michael@0: avoidCGCrashes:(BOOL)aAvoidCGCrashes michael@0: { michael@0: mDrawFunc = aFunc; michael@0: mPluginInstance = aPluginInstance; michael@0: mAvoidCGCrashes = aAvoidCGCrashes; michael@0: mLastCGContext = nil; michael@0: } michael@0: michael@0: // The Flash plugin, in very unusual circumstances, can (in CoreGraphics michael@0: // mode) try to access the CGContextRef from -[CGBridgeLayer drawInContext:] michael@0: // outside of any call to NPP_HandleEvent(NPCocoaEventDrawRect). This usually michael@0: // crashes the plugin process (probably because it tries to access deleted michael@0: // memory). We stop these crashes from happening by holding a reference to michael@0: // the CGContextRef, and also by ensuring that it's data won't get deleted. michael@0: // The CGContextRef won't "work" in this form. But this won't cause trouble michael@0: // for plugins that do things correctly (that don't access this CGContextRef michael@0: // outside of the call to NPP_HandleEvent() that passes it to the plugin). michael@0: // The OS may reuse this CGContextRef (it may get passed to other calls to michael@0: // -[CGBridgeLayer drawInContext:]). But before each call the OS calls michael@0: // CGBitmapContextSetData() to replace its data, which undoes the changes michael@0: // we make here. See bug 804606. michael@0: - (void)protectLastCGContext michael@0: { michael@0: if (!mAvoidCGCrashes || !mLastCGContext) { michael@0: return; michael@0: } michael@0: michael@0: static char ensuredData[128] = {0}; michael@0: michael@0: if (!CGBitmapContextSetDataPtr) { michael@0: CGBitmapContextSetDataPtr = (CGBitmapContextSetDataFunc) michael@0: dlsym(RTLD_DEFAULT, "CGBitmapContextSetData"); michael@0: } michael@0: michael@0: if (CGBitmapContextSetDataPtr && (GetContextType(mLastCGContext) == CG_CONTEXT_TYPE_BITMAP)) { michael@0: CGBitmapContextSetDataPtr(mLastCGContext, 0, 0, 1, 1, ensuredData, 8, 32, 64); michael@0: } michael@0: } michael@0: michael@0: - (void)drawInContext:(CGContextRef)aCGContext michael@0: { michael@0: ::CGContextSaveGState(aCGContext); michael@0: ::CGContextTranslateCTM(aCGContext, 0, self.bounds.size.height); michael@0: ::CGContextScaleCTM(aCGContext, (CGFloat) 1, (CGFloat) -1); michael@0: michael@0: mUpdateRect = nsIntRect(0, 0, self.bounds.size.width, self.bounds.size.height); michael@0: michael@0: mDrawFunc(aCGContext, mPluginInstance, mUpdateRect); michael@0: michael@0: ::CGContextRestoreGState(aCGContext); michael@0: michael@0: if (mAvoidCGCrashes) { michael@0: if (mLastCGContext) { michael@0: ::CGContextRelease(mLastCGContext); michael@0: } michael@0: mLastCGContext = aCGContext; michael@0: ::CGContextRetain(mLastCGContext); michael@0: } michael@0: michael@0: mUpdateRect.SetEmpty(); michael@0: } michael@0: michael@0: - (void)dealloc michael@0: { michael@0: if (mLastCGContext) { michael@0: ::CGContextRelease(mLastCGContext); michael@0: } michael@0: [super dealloc]; michael@0: } michael@0: michael@0: @end michael@0: michael@0: void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, michael@0: bool aAvoidCGCrashes, double aContentsScaleFactor) michael@0: { michael@0: CGBridgeLayer *bridgeLayer = [[CGBridgeLayer alloc] init]; michael@0: michael@0: // We need to make bridgeLayer behave properly when its superlayer changes michael@0: // size (in nsCARenderer::SetBounds()). michael@0: bridgeLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; michael@0: bridgeLayer.needsDisplayOnBoundsChange = YES; michael@0: NSNull *nullValue = [NSNull null]; michael@0: NSDictionary *actions = [NSDictionary dictionaryWithObjectsAndKeys: michael@0: nullValue, @"bounds", michael@0: nullValue, @"contents", michael@0: nullValue, @"contentsRect", michael@0: nullValue, @"position", michael@0: nil]; michael@0: [bridgeLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]]; michael@0: michael@0: // For reasons that aren't clear (perhaps one or more OS bugs), we can only michael@0: // use full HiDPI resolution here if the tree is built with the 10.7 SDK or michael@0: // up. If we build with the 10.6 SDK, changing the contentsScale property michael@0: // of bridgeLayer (even to the same value) causes it to stop working (go michael@0: // blank). This doesn't happen with objects that are members of the CALayer michael@0: // class (as opposed to one of its subclasses). michael@0: #if defined(MAC_OS_X_VERSION_10_7) && \ michael@0: MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 michael@0: if ([bridgeLayer respondsToSelector:@selector(setContentsScale:)]) { michael@0: bridgeLayer.contentsScale = aContentsScaleFactor; michael@0: } michael@0: #endif michael@0: michael@0: [bridgeLayer setDrawFunc:aFunc michael@0: pluginInstance:aPluginInstance michael@0: avoidCGCrashes:aAvoidCGCrashes]; michael@0: return bridgeLayer; michael@0: } michael@0: michael@0: void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void *cgLayer) { michael@0: CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)cgLayer; michael@0: [bridgeLayer release]; michael@0: } michael@0: michael@0: void mozilla::plugins::PluginUtilsOSX::Repaint(void *caLayer, nsIntRect aRect) { michael@0: CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)caLayer; michael@0: [CATransaction begin]; michael@0: [bridgeLayer updateRect:aRect]; michael@0: [bridgeLayer setNeedsDisplay]; michael@0: [bridgeLayer displayIfNeeded]; michael@0: [CATransaction commit]; michael@0: [bridgeLayer protectLastCGContext]; michael@0: } michael@0: michael@0: @interface EventProcessor : NSObject { michael@0: RemoteProcessEvents aRemoteEvents; michael@0: void *aPluginModule; michael@0: } michael@0: - (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule; michael@0: - (void)onTick; michael@0: @end michael@0: michael@0: @implementation EventProcessor michael@0: - (void) onTick michael@0: { michael@0: aRemoteEvents(aPluginModule); michael@0: } michael@0: michael@0: - (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule michael@0: { michael@0: aRemoteEvents = remoteEvents; michael@0: aPluginModule = pluginModule; michael@0: } michael@0: @end michael@0: michael@0: #define EVENT_PROCESS_DELAY 0.05 // 50 ms michael@0: michael@0: NPError mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // Set the native cursor to the OS default (an arrow) before displaying the michael@0: // context menu. Otherwise (if the plugin has changed the cursor) it may michael@0: // stay as the plugin has set it -- which means it may be invisible. We michael@0: // need to do this because we display the context menu without making the michael@0: // plugin process the foreground process. If we did, the cursor would michael@0: // change to an arrow cursor automatically -- as it does in Chrome. michael@0: [[NSCursor arrowCursor] set]; michael@0: michael@0: // Create a timer to process browser events while waiting michael@0: // on the menu. This prevents the browser from hanging michael@0: // during the lifetime of the menu. michael@0: EventProcessor* eventProcessor = [[EventProcessor alloc] init]; michael@0: [eventProcessor setRemoteEvents:remoteEvent pluginModule:pluginModule]; michael@0: NSTimer *eventTimer = [NSTimer timerWithTimeInterval:EVENT_PROCESS_DELAY michael@0: target:eventProcessor selector:@selector(onTick) michael@0: userInfo:nil repeats:TRUE]; michael@0: // Use NSEventTrackingRunLoopMode otherwise the timer will michael@0: // not fire during the right click menu. michael@0: [[NSRunLoop currentRunLoop] addTimer:eventTimer michael@0: forMode:NSEventTrackingRunLoopMode]; michael@0: michael@0: NSMenu* nsmenu = reinterpret_cast(aMenu); michael@0: NSPoint screen_point = ::NSMakePoint(aX, aY); michael@0: michael@0: [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil]; michael@0: michael@0: [eventTimer invalidate]; michael@0: [eventProcessor release]; michael@0: michael@0: return NPERR_NO_ERROR; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NPERR_GENERIC_ERROR); michael@0: } michael@0: michael@0: void mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: ::CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true); michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: michael@0: #define UNDOCUMENTED_SESSION_CONSTANT ((int)-2) michael@0: namespace mozilla { michael@0: namespace plugins { michael@0: namespace PluginUtilsOSX { michael@0: static void *sApplicationASN = NULL; michael@0: static void *sApplicationInfoItem = NULL; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool mozilla::plugins::PluginUtilsOSX::SetProcessName(const char* aProcessName) { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: nsAutoreleasePool localPool; michael@0: michael@0: if (!aProcessName || strcmp(aProcessName, "") == 0) { michael@0: return false; michael@0: } michael@0: michael@0: NSString *currentName = [[[NSBundle mainBundle] localizedInfoDictionary] michael@0: objectForKey:(NSString *)kCFBundleNameKey]; michael@0: michael@0: char formattedName[1024]; michael@0: snprintf(formattedName, sizeof(formattedName), michael@0: "%s (%s)", [currentName UTF8String], aProcessName); michael@0: michael@0: aProcessName = formattedName; michael@0: michael@0: // This function is based on Chrome/Webkit's and relies on potentially dangerous SPI. michael@0: typedef CFTypeRef (*LSGetASNType)(); michael@0: typedef OSStatus (*LSSetInformationItemType)(int, CFTypeRef, michael@0: CFStringRef, michael@0: CFStringRef, michael@0: CFDictionaryRef*); michael@0: michael@0: CFBundleRef launchServices = ::CFBundleGetBundleWithIdentifier( michael@0: CFSTR("com.apple.LaunchServices")); michael@0: if (!launchServices) { michael@0: NS_WARNING("Failed to set process name: Could not open LaunchServices bundle"); michael@0: return false; michael@0: } michael@0: michael@0: if (!sApplicationASN) { michael@0: sApplicationASN = ::CFBundleGetFunctionPointerForName(launchServices, michael@0: CFSTR("_LSGetCurrentApplicationASN")); michael@0: } michael@0: michael@0: LSGetASNType getASNFunc = reinterpret_cast michael@0: (sApplicationASN); michael@0: michael@0: if (!sApplicationInfoItem) { michael@0: sApplicationInfoItem = ::CFBundleGetFunctionPointerForName(launchServices, michael@0: CFSTR("_LSSetApplicationInformationItem")); michael@0: } michael@0: michael@0: LSSetInformationItemType setInformationItemFunc michael@0: = reinterpret_cast michael@0: (sApplicationInfoItem); michael@0: michael@0: void * displayNameKeyAddr = ::CFBundleGetDataPointerForName(launchServices, michael@0: CFSTR("_kLSDisplayNameKey")); michael@0: michael@0: CFStringRef displayNameKey = nil; michael@0: if (displayNameKeyAddr) { michael@0: displayNameKey = reinterpret_cast(*(CFStringRef*)displayNameKeyAddr); michael@0: } michael@0: michael@0: // Rename will fail without this michael@0: ProcessSerialNumber psn; michael@0: if (::GetCurrentProcess(&psn) != noErr) { michael@0: return false; michael@0: } michael@0: michael@0: CFTypeRef currentAsn = getASNFunc(); michael@0: michael@0: if (!getASNFunc || !setInformationItemFunc || michael@0: !displayNameKey || !currentAsn) { michael@0: NS_WARNING("Failed to set process name: Accessing launchServices failed"); michael@0: return false; michael@0: } michael@0: michael@0: CFStringRef processName = ::CFStringCreateWithCString(nil, michael@0: aProcessName, michael@0: kCFStringEncodingASCII); michael@0: if (!processName) { michael@0: NS_WARNING("Failed to set process name: Could not create CFStringRef"); michael@0: return false; michael@0: } michael@0: michael@0: OSErr err = setInformationItemFunc(UNDOCUMENTED_SESSION_CONSTANT, currentAsn, michael@0: displayNameKey, processName, michael@0: nil); // Optional out param michael@0: ::CFRelease(processName); michael@0: if (err != noErr) { michael@0: NS_WARNING("Failed to set process name: LSSetInformationItemType err"); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); michael@0: } michael@0: michael@0: namespace mozilla { michael@0: namespace plugins { michael@0: namespace PluginUtilsOSX { michael@0: michael@0: size_t nsDoubleBufferCARenderer::GetFrontSurfaceWidth() { michael@0: if (!HasFrontSurface()) { michael@0: return 0; michael@0: } michael@0: michael@0: return mFrontSurface->GetWidth(); michael@0: } michael@0: michael@0: size_t nsDoubleBufferCARenderer::GetFrontSurfaceHeight() { michael@0: if (!HasFrontSurface()) { michael@0: return 0; michael@0: } michael@0: michael@0: return mFrontSurface->GetHeight(); michael@0: } michael@0: michael@0: double nsDoubleBufferCARenderer::GetFrontSurfaceContentsScaleFactor() { michael@0: if (!HasFrontSurface()) { michael@0: return 1.0; michael@0: } michael@0: michael@0: return mFrontSurface->GetContentsScaleFactor(); michael@0: } michael@0: michael@0: size_t nsDoubleBufferCARenderer::GetBackSurfaceWidth() { michael@0: if (!HasBackSurface()) { michael@0: return 0; michael@0: } michael@0: michael@0: return mBackSurface->GetWidth(); michael@0: } michael@0: michael@0: size_t nsDoubleBufferCARenderer::GetBackSurfaceHeight() { michael@0: if (!HasBackSurface()) { michael@0: return 0; michael@0: } michael@0: michael@0: return mBackSurface->GetHeight(); michael@0: } michael@0: michael@0: double nsDoubleBufferCARenderer::GetBackSurfaceContentsScaleFactor() { michael@0: if (!HasBackSurface()) { michael@0: return 1.0; michael@0: } michael@0: michael@0: return mBackSurface->GetContentsScaleFactor(); michael@0: } michael@0: michael@0: IOSurfaceID nsDoubleBufferCARenderer::GetFrontSurfaceID() { michael@0: if (!HasFrontSurface()) { michael@0: return 0; michael@0: } michael@0: michael@0: return mFrontSurface->GetIOSurfaceID(); michael@0: } michael@0: michael@0: bool nsDoubleBufferCARenderer::HasBackSurface() { michael@0: return !!mBackSurface; michael@0: } michael@0: michael@0: bool nsDoubleBufferCARenderer::HasFrontSurface() { michael@0: return !!mFrontSurface; michael@0: } michael@0: michael@0: bool nsDoubleBufferCARenderer::HasCALayer() { michael@0: return !!mCALayer; michael@0: } michael@0: michael@0: void nsDoubleBufferCARenderer::SetCALayer(void *aCALayer) { michael@0: mCALayer = aCALayer; michael@0: } michael@0: michael@0: bool nsDoubleBufferCARenderer::InitFrontSurface(size_t aWidth, size_t aHeight, michael@0: double aContentsScaleFactor, michael@0: AllowOfflineRendererEnum aAllowOfflineRenderer) { michael@0: if (!mCALayer) { michael@0: return false; michael@0: } michael@0: michael@0: mContentsScaleFactor = aContentsScaleFactor; michael@0: mFrontSurface = MacIOSurface::CreateIOSurface(aWidth, aHeight, mContentsScaleFactor); michael@0: if (!mFrontSurface) { michael@0: mCARenderer = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: if (!mCARenderer) { michael@0: mCARenderer = new nsCARenderer(); michael@0: if (!mCARenderer) { michael@0: mFrontSurface = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: mCARenderer->AttachIOSurface(mFrontSurface); michael@0: michael@0: nsresult result = mCARenderer->SetupRenderer(mCALayer, michael@0: mFrontSurface->GetWidth(), michael@0: mFrontSurface->GetHeight(), michael@0: mContentsScaleFactor, michael@0: aAllowOfflineRenderer); michael@0: michael@0: if (result != NS_OK) { michael@0: mCARenderer = nullptr; michael@0: mFrontSurface = nullptr; michael@0: return false; michael@0: } michael@0: } else { michael@0: mCARenderer->AttachIOSurface(mFrontSurface); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void nsDoubleBufferCARenderer::Render() { michael@0: if (!HasFrontSurface() || !mCARenderer) { michael@0: return; michael@0: } michael@0: michael@0: mCARenderer->Render(GetFrontSurfaceWidth(), GetFrontSurfaceHeight(), michael@0: mContentsScaleFactor, nullptr); michael@0: } michael@0: michael@0: void nsDoubleBufferCARenderer::SwapSurfaces() { michael@0: RefPtr prevFrontSurface = mFrontSurface; michael@0: mFrontSurface = mBackSurface; michael@0: mBackSurface = prevFrontSurface; michael@0: michael@0: if (mFrontSurface) { michael@0: mCARenderer->AttachIOSurface(mFrontSurface); michael@0: } michael@0: } michael@0: michael@0: void nsDoubleBufferCARenderer::ClearFrontSurface() { michael@0: mFrontSurface = nullptr; michael@0: if (!mFrontSurface && !mBackSurface) { michael@0: mCARenderer = nullptr; michael@0: } michael@0: } michael@0: michael@0: void nsDoubleBufferCARenderer::ClearBackSurface() { michael@0: mBackSurface = nullptr; michael@0: if (!mFrontSurface && !mBackSurface) { michael@0: mCARenderer = nullptr; michael@0: } michael@0: } michael@0: michael@0: } //PluginUtilsOSX michael@0: } //plugins michael@0: } //mozilla michael@0: