1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/plugins/ipc/PluginUtilsOSX.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,511 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +// vim:set ts=2 sts=2 sw=2 et cin: 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include <dlfcn.h> 1.11 +#import <AppKit/AppKit.h> 1.12 +#import <QuartzCore/QuartzCore.h> 1.13 +#include "PluginUtilsOSX.h" 1.14 + 1.15 +// Remove definitions for try/catch interfering with ObjCException macros. 1.16 +#include "nsObjCExceptions.h" 1.17 +#include "nsCocoaUtils.h" 1.18 + 1.19 +#include "nsDebug.h" 1.20 + 1.21 +@interface CALayer (ContentsScale) 1.22 +- (double)contentsScale; 1.23 +- (void)setContentsScale:(double)scale; 1.24 +@end 1.25 + 1.26 +using namespace mozilla::plugins::PluginUtilsOSX; 1.27 + 1.28 +@interface CGBridgeLayer : CALayer { 1.29 + DrawPluginFunc mDrawFunc; 1.30 + void* mPluginInstance; 1.31 + nsIntRect mUpdateRect; 1.32 + BOOL mAvoidCGCrashes; 1.33 + CGContextRef mLastCGContext; 1.34 +} 1.35 +- (void)setDrawFunc:(DrawPluginFunc)aFunc 1.36 + pluginInstance:(void*)aPluginInstance 1.37 + avoidCGCrashes:(BOOL)aAvoidCGCrashes; 1.38 +- (void)updateRect:(nsIntRect)aRect; 1.39 +- (void)protectLastCGContext; 1.40 + 1.41 +@end 1.42 + 1.43 +// CGBitmapContextSetData() is an undocumented function present (with 1.44 +// the same signature) since at least OS X 10.5. As the name suggests, 1.45 +// it's used to replace the "data" in a bitmap context that was 1.46 +// originally specified in a call to CGBitmapContextCreate() or 1.47 +// CGBitmapContextCreateWithData(). 1.48 +typedef void (*CGBitmapContextSetDataFunc) (CGContextRef c, 1.49 + size_t x, 1.50 + size_t y, 1.51 + size_t width, 1.52 + size_t height, 1.53 + void* data, 1.54 + size_t bitsPerComponent, 1.55 + size_t bitsPerPixel, 1.56 + size_t bytesPerRow); 1.57 +CGBitmapContextSetDataFunc CGBitmapContextSetDataPtr = NULL; 1.58 + 1.59 +@implementation CGBridgeLayer 1.60 +- (void) updateRect:(nsIntRect)aRect 1.61 +{ 1.62 + mUpdateRect.UnionRect(mUpdateRect, aRect); 1.63 +} 1.64 + 1.65 +- (void) setDrawFunc:(DrawPluginFunc)aFunc 1.66 + pluginInstance:(void*)aPluginInstance 1.67 + avoidCGCrashes:(BOOL)aAvoidCGCrashes 1.68 +{ 1.69 + mDrawFunc = aFunc; 1.70 + mPluginInstance = aPluginInstance; 1.71 + mAvoidCGCrashes = aAvoidCGCrashes; 1.72 + mLastCGContext = nil; 1.73 +} 1.74 + 1.75 +// The Flash plugin, in very unusual circumstances, can (in CoreGraphics 1.76 +// mode) try to access the CGContextRef from -[CGBridgeLayer drawInContext:] 1.77 +// outside of any call to NPP_HandleEvent(NPCocoaEventDrawRect). This usually 1.78 +// crashes the plugin process (probably because it tries to access deleted 1.79 +// memory). We stop these crashes from happening by holding a reference to 1.80 +// the CGContextRef, and also by ensuring that it's data won't get deleted. 1.81 +// The CGContextRef won't "work" in this form. But this won't cause trouble 1.82 +// for plugins that do things correctly (that don't access this CGContextRef 1.83 +// outside of the call to NPP_HandleEvent() that passes it to the plugin). 1.84 +// The OS may reuse this CGContextRef (it may get passed to other calls to 1.85 +// -[CGBridgeLayer drawInContext:]). But before each call the OS calls 1.86 +// CGBitmapContextSetData() to replace its data, which undoes the changes 1.87 +// we make here. See bug 804606. 1.88 +- (void)protectLastCGContext 1.89 +{ 1.90 + if (!mAvoidCGCrashes || !mLastCGContext) { 1.91 + return; 1.92 + } 1.93 + 1.94 + static char ensuredData[128] = {0}; 1.95 + 1.96 + if (!CGBitmapContextSetDataPtr) { 1.97 + CGBitmapContextSetDataPtr = (CGBitmapContextSetDataFunc) 1.98 + dlsym(RTLD_DEFAULT, "CGBitmapContextSetData"); 1.99 + } 1.100 + 1.101 + if (CGBitmapContextSetDataPtr && (GetContextType(mLastCGContext) == CG_CONTEXT_TYPE_BITMAP)) { 1.102 + CGBitmapContextSetDataPtr(mLastCGContext, 0, 0, 1, 1, ensuredData, 8, 32, 64); 1.103 + } 1.104 +} 1.105 + 1.106 +- (void)drawInContext:(CGContextRef)aCGContext 1.107 +{ 1.108 + ::CGContextSaveGState(aCGContext); 1.109 + ::CGContextTranslateCTM(aCGContext, 0, self.bounds.size.height); 1.110 + ::CGContextScaleCTM(aCGContext, (CGFloat) 1, (CGFloat) -1); 1.111 + 1.112 + mUpdateRect = nsIntRect(0, 0, self.bounds.size.width, self.bounds.size.height); 1.113 + 1.114 + mDrawFunc(aCGContext, mPluginInstance, mUpdateRect); 1.115 + 1.116 + ::CGContextRestoreGState(aCGContext); 1.117 + 1.118 + if (mAvoidCGCrashes) { 1.119 + if (mLastCGContext) { 1.120 + ::CGContextRelease(mLastCGContext); 1.121 + } 1.122 + mLastCGContext = aCGContext; 1.123 + ::CGContextRetain(mLastCGContext); 1.124 + } 1.125 + 1.126 + mUpdateRect.SetEmpty(); 1.127 +} 1.128 + 1.129 +- (void)dealloc 1.130 +{ 1.131 + if (mLastCGContext) { 1.132 + ::CGContextRelease(mLastCGContext); 1.133 + } 1.134 + [super dealloc]; 1.135 +} 1.136 + 1.137 +@end 1.138 + 1.139 +void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, 1.140 + bool aAvoidCGCrashes, double aContentsScaleFactor) 1.141 +{ 1.142 + CGBridgeLayer *bridgeLayer = [[CGBridgeLayer alloc] init]; 1.143 + 1.144 + // We need to make bridgeLayer behave properly when its superlayer changes 1.145 + // size (in nsCARenderer::SetBounds()). 1.146 + bridgeLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; 1.147 + bridgeLayer.needsDisplayOnBoundsChange = YES; 1.148 + NSNull *nullValue = [NSNull null]; 1.149 + NSDictionary *actions = [NSDictionary dictionaryWithObjectsAndKeys: 1.150 + nullValue, @"bounds", 1.151 + nullValue, @"contents", 1.152 + nullValue, @"contentsRect", 1.153 + nullValue, @"position", 1.154 + nil]; 1.155 + [bridgeLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]]; 1.156 + 1.157 + // For reasons that aren't clear (perhaps one or more OS bugs), we can only 1.158 + // use full HiDPI resolution here if the tree is built with the 10.7 SDK or 1.159 + // up. If we build with the 10.6 SDK, changing the contentsScale property 1.160 + // of bridgeLayer (even to the same value) causes it to stop working (go 1.161 + // blank). This doesn't happen with objects that are members of the CALayer 1.162 + // class (as opposed to one of its subclasses). 1.163 +#if defined(MAC_OS_X_VERSION_10_7) && \ 1.164 + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 1.165 + if ([bridgeLayer respondsToSelector:@selector(setContentsScale:)]) { 1.166 + bridgeLayer.contentsScale = aContentsScaleFactor; 1.167 + } 1.168 +#endif 1.169 + 1.170 + [bridgeLayer setDrawFunc:aFunc 1.171 + pluginInstance:aPluginInstance 1.172 + avoidCGCrashes:aAvoidCGCrashes]; 1.173 + return bridgeLayer; 1.174 +} 1.175 + 1.176 +void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void *cgLayer) { 1.177 + CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)cgLayer; 1.178 + [bridgeLayer release]; 1.179 +} 1.180 + 1.181 +void mozilla::plugins::PluginUtilsOSX::Repaint(void *caLayer, nsIntRect aRect) { 1.182 + CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)caLayer; 1.183 + [CATransaction begin]; 1.184 + [bridgeLayer updateRect:aRect]; 1.185 + [bridgeLayer setNeedsDisplay]; 1.186 + [bridgeLayer displayIfNeeded]; 1.187 + [CATransaction commit]; 1.188 + [bridgeLayer protectLastCGContext]; 1.189 +} 1.190 + 1.191 +@interface EventProcessor : NSObject { 1.192 + RemoteProcessEvents aRemoteEvents; 1.193 + void *aPluginModule; 1.194 +} 1.195 +- (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule; 1.196 +- (void)onTick; 1.197 +@end 1.198 + 1.199 +@implementation EventProcessor 1.200 +- (void) onTick 1.201 +{ 1.202 + aRemoteEvents(aPluginModule); 1.203 +} 1.204 + 1.205 +- (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule 1.206 +{ 1.207 + aRemoteEvents = remoteEvents; 1.208 + aPluginModule = pluginModule; 1.209 +} 1.210 +@end 1.211 + 1.212 +#define EVENT_PROCESS_DELAY 0.05 // 50 ms 1.213 + 1.214 +NPError mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent) 1.215 +{ 1.216 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.217 + 1.218 + // Set the native cursor to the OS default (an arrow) before displaying the 1.219 + // context menu. Otherwise (if the plugin has changed the cursor) it may 1.220 + // stay as the plugin has set it -- which means it may be invisible. We 1.221 + // need to do this because we display the context menu without making the 1.222 + // plugin process the foreground process. If we did, the cursor would 1.223 + // change to an arrow cursor automatically -- as it does in Chrome. 1.224 + [[NSCursor arrowCursor] set]; 1.225 + 1.226 + // Create a timer to process browser events while waiting 1.227 + // on the menu. This prevents the browser from hanging 1.228 + // during the lifetime of the menu. 1.229 + EventProcessor* eventProcessor = [[EventProcessor alloc] init]; 1.230 + [eventProcessor setRemoteEvents:remoteEvent pluginModule:pluginModule]; 1.231 + NSTimer *eventTimer = [NSTimer timerWithTimeInterval:EVENT_PROCESS_DELAY 1.232 + target:eventProcessor selector:@selector(onTick) 1.233 + userInfo:nil repeats:TRUE]; 1.234 + // Use NSEventTrackingRunLoopMode otherwise the timer will 1.235 + // not fire during the right click menu. 1.236 + [[NSRunLoop currentRunLoop] addTimer:eventTimer 1.237 + forMode:NSEventTrackingRunLoopMode]; 1.238 + 1.239 + NSMenu* nsmenu = reinterpret_cast<NSMenu*>(aMenu); 1.240 + NSPoint screen_point = ::NSMakePoint(aX, aY); 1.241 + 1.242 + [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil]; 1.243 + 1.244 + [eventTimer invalidate]; 1.245 + [eventProcessor release]; 1.246 + 1.247 + return NPERR_NO_ERROR; 1.248 + 1.249 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NPERR_GENERIC_ERROR); 1.250 +} 1.251 + 1.252 +void mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop() 1.253 +{ 1.254 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.255 + ::CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true); 1.256 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.257 +} 1.258 + 1.259 + 1.260 +#define UNDOCUMENTED_SESSION_CONSTANT ((int)-2) 1.261 +namespace mozilla { 1.262 +namespace plugins { 1.263 +namespace PluginUtilsOSX { 1.264 + static void *sApplicationASN = NULL; 1.265 + static void *sApplicationInfoItem = NULL; 1.266 +} 1.267 +} 1.268 +} 1.269 + 1.270 +bool mozilla::plugins::PluginUtilsOSX::SetProcessName(const char* aProcessName) { 1.271 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.272 + nsAutoreleasePool localPool; 1.273 + 1.274 + if (!aProcessName || strcmp(aProcessName, "") == 0) { 1.275 + return false; 1.276 + } 1.277 + 1.278 + NSString *currentName = [[[NSBundle mainBundle] localizedInfoDictionary] 1.279 + objectForKey:(NSString *)kCFBundleNameKey]; 1.280 + 1.281 + char formattedName[1024]; 1.282 + snprintf(formattedName, sizeof(formattedName), 1.283 + "%s (%s)", [currentName UTF8String], aProcessName); 1.284 + 1.285 + aProcessName = formattedName; 1.286 + 1.287 + // This function is based on Chrome/Webkit's and relies on potentially dangerous SPI. 1.288 + typedef CFTypeRef (*LSGetASNType)(); 1.289 + typedef OSStatus (*LSSetInformationItemType)(int, CFTypeRef, 1.290 + CFStringRef, 1.291 + CFStringRef, 1.292 + CFDictionaryRef*); 1.293 + 1.294 + CFBundleRef launchServices = ::CFBundleGetBundleWithIdentifier( 1.295 + CFSTR("com.apple.LaunchServices")); 1.296 + if (!launchServices) { 1.297 + NS_WARNING("Failed to set process name: Could not open LaunchServices bundle"); 1.298 + return false; 1.299 + } 1.300 + 1.301 + if (!sApplicationASN) { 1.302 + sApplicationASN = ::CFBundleGetFunctionPointerForName(launchServices, 1.303 + CFSTR("_LSGetCurrentApplicationASN")); 1.304 + } 1.305 + 1.306 + LSGetASNType getASNFunc = reinterpret_cast<LSGetASNType> 1.307 + (sApplicationASN); 1.308 + 1.309 + if (!sApplicationInfoItem) { 1.310 + sApplicationInfoItem = ::CFBundleGetFunctionPointerForName(launchServices, 1.311 + CFSTR("_LSSetApplicationInformationItem")); 1.312 + } 1.313 + 1.314 + LSSetInformationItemType setInformationItemFunc 1.315 + = reinterpret_cast<LSSetInformationItemType> 1.316 + (sApplicationInfoItem); 1.317 + 1.318 + void * displayNameKeyAddr = ::CFBundleGetDataPointerForName(launchServices, 1.319 + CFSTR("_kLSDisplayNameKey")); 1.320 + 1.321 + CFStringRef displayNameKey = nil; 1.322 + if (displayNameKeyAddr) { 1.323 + displayNameKey = reinterpret_cast<CFStringRef>(*(CFStringRef*)displayNameKeyAddr); 1.324 + } 1.325 + 1.326 + // Rename will fail without this 1.327 + ProcessSerialNumber psn; 1.328 + if (::GetCurrentProcess(&psn) != noErr) { 1.329 + return false; 1.330 + } 1.331 + 1.332 + CFTypeRef currentAsn = getASNFunc(); 1.333 + 1.334 + if (!getASNFunc || !setInformationItemFunc || 1.335 + !displayNameKey || !currentAsn) { 1.336 + NS_WARNING("Failed to set process name: Accessing launchServices failed"); 1.337 + return false; 1.338 + } 1.339 + 1.340 + CFStringRef processName = ::CFStringCreateWithCString(nil, 1.341 + aProcessName, 1.342 + kCFStringEncodingASCII); 1.343 + if (!processName) { 1.344 + NS_WARNING("Failed to set process name: Could not create CFStringRef"); 1.345 + return false; 1.346 + } 1.347 + 1.348 + OSErr err = setInformationItemFunc(UNDOCUMENTED_SESSION_CONSTANT, currentAsn, 1.349 + displayNameKey, processName, 1.350 + nil); // Optional out param 1.351 + ::CFRelease(processName); 1.352 + if (err != noErr) { 1.353 + NS_WARNING("Failed to set process name: LSSetInformationItemType err"); 1.354 + return false; 1.355 + } 1.356 + 1.357 + return true; 1.358 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); 1.359 +} 1.360 + 1.361 +namespace mozilla { 1.362 +namespace plugins { 1.363 +namespace PluginUtilsOSX { 1.364 + 1.365 +size_t nsDoubleBufferCARenderer::GetFrontSurfaceWidth() { 1.366 + if (!HasFrontSurface()) { 1.367 + return 0; 1.368 + } 1.369 + 1.370 + return mFrontSurface->GetWidth(); 1.371 +} 1.372 + 1.373 +size_t nsDoubleBufferCARenderer::GetFrontSurfaceHeight() { 1.374 + if (!HasFrontSurface()) { 1.375 + return 0; 1.376 + } 1.377 + 1.378 + return mFrontSurface->GetHeight(); 1.379 +} 1.380 + 1.381 +double nsDoubleBufferCARenderer::GetFrontSurfaceContentsScaleFactor() { 1.382 + if (!HasFrontSurface()) { 1.383 + return 1.0; 1.384 + } 1.385 + 1.386 + return mFrontSurface->GetContentsScaleFactor(); 1.387 +} 1.388 + 1.389 +size_t nsDoubleBufferCARenderer::GetBackSurfaceWidth() { 1.390 + if (!HasBackSurface()) { 1.391 + return 0; 1.392 + } 1.393 + 1.394 + return mBackSurface->GetWidth(); 1.395 +} 1.396 + 1.397 +size_t nsDoubleBufferCARenderer::GetBackSurfaceHeight() { 1.398 + if (!HasBackSurface()) { 1.399 + return 0; 1.400 + } 1.401 + 1.402 + return mBackSurface->GetHeight(); 1.403 +} 1.404 + 1.405 +double nsDoubleBufferCARenderer::GetBackSurfaceContentsScaleFactor() { 1.406 + if (!HasBackSurface()) { 1.407 + return 1.0; 1.408 + } 1.409 + 1.410 + return mBackSurface->GetContentsScaleFactor(); 1.411 +} 1.412 + 1.413 +IOSurfaceID nsDoubleBufferCARenderer::GetFrontSurfaceID() { 1.414 + if (!HasFrontSurface()) { 1.415 + return 0; 1.416 + } 1.417 + 1.418 + return mFrontSurface->GetIOSurfaceID(); 1.419 +} 1.420 + 1.421 +bool nsDoubleBufferCARenderer::HasBackSurface() { 1.422 + return !!mBackSurface; 1.423 +} 1.424 + 1.425 +bool nsDoubleBufferCARenderer::HasFrontSurface() { 1.426 + return !!mFrontSurface; 1.427 +} 1.428 + 1.429 +bool nsDoubleBufferCARenderer::HasCALayer() { 1.430 + return !!mCALayer; 1.431 +} 1.432 + 1.433 +void nsDoubleBufferCARenderer::SetCALayer(void *aCALayer) { 1.434 + mCALayer = aCALayer; 1.435 +} 1.436 + 1.437 +bool nsDoubleBufferCARenderer::InitFrontSurface(size_t aWidth, size_t aHeight, 1.438 + double aContentsScaleFactor, 1.439 + AllowOfflineRendererEnum aAllowOfflineRenderer) { 1.440 + if (!mCALayer) { 1.441 + return false; 1.442 + } 1.443 + 1.444 + mContentsScaleFactor = aContentsScaleFactor; 1.445 + mFrontSurface = MacIOSurface::CreateIOSurface(aWidth, aHeight, mContentsScaleFactor); 1.446 + if (!mFrontSurface) { 1.447 + mCARenderer = nullptr; 1.448 + return false; 1.449 + } 1.450 + 1.451 + if (!mCARenderer) { 1.452 + mCARenderer = new nsCARenderer(); 1.453 + if (!mCARenderer) { 1.454 + mFrontSurface = nullptr; 1.455 + return false; 1.456 + } 1.457 + 1.458 + mCARenderer->AttachIOSurface(mFrontSurface); 1.459 + 1.460 + nsresult result = mCARenderer->SetupRenderer(mCALayer, 1.461 + mFrontSurface->GetWidth(), 1.462 + mFrontSurface->GetHeight(), 1.463 + mContentsScaleFactor, 1.464 + aAllowOfflineRenderer); 1.465 + 1.466 + if (result != NS_OK) { 1.467 + mCARenderer = nullptr; 1.468 + mFrontSurface = nullptr; 1.469 + return false; 1.470 + } 1.471 + } else { 1.472 + mCARenderer->AttachIOSurface(mFrontSurface); 1.473 + } 1.474 + 1.475 + return true; 1.476 +} 1.477 + 1.478 +void nsDoubleBufferCARenderer::Render() { 1.479 + if (!HasFrontSurface() || !mCARenderer) { 1.480 + return; 1.481 + } 1.482 + 1.483 + mCARenderer->Render(GetFrontSurfaceWidth(), GetFrontSurfaceHeight(), 1.484 + mContentsScaleFactor, nullptr); 1.485 +} 1.486 + 1.487 +void nsDoubleBufferCARenderer::SwapSurfaces() { 1.488 + RefPtr<MacIOSurface> prevFrontSurface = mFrontSurface; 1.489 + mFrontSurface = mBackSurface; 1.490 + mBackSurface = prevFrontSurface; 1.491 + 1.492 + if (mFrontSurface) { 1.493 + mCARenderer->AttachIOSurface(mFrontSurface); 1.494 + } 1.495 +} 1.496 + 1.497 +void nsDoubleBufferCARenderer::ClearFrontSurface() { 1.498 + mFrontSurface = nullptr; 1.499 + if (!mFrontSurface && !mBackSurface) { 1.500 + mCARenderer = nullptr; 1.501 + } 1.502 +} 1.503 + 1.504 +void nsDoubleBufferCARenderer::ClearBackSurface() { 1.505 + mBackSurface = nullptr; 1.506 + if (!mFrontSurface && !mBackSurface) { 1.507 + mCARenderer = nullptr; 1.508 + } 1.509 +} 1.510 + 1.511 +} //PluginUtilsOSX 1.512 +} //plugins 1.513 +} //mozilla 1.514 +