dom/plugins/ipc/PluginUtilsOSX.mm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 // vim:set ts=2 sts=2 sw=2 et cin:
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include <dlfcn.h>
     8 #import <AppKit/AppKit.h>
     9 #import <QuartzCore/QuartzCore.h>
    10 #include "PluginUtilsOSX.h"
    12 // Remove definitions for try/catch interfering with ObjCException macros.
    13 #include "nsObjCExceptions.h"
    14 #include "nsCocoaUtils.h"
    16 #include "nsDebug.h"
    18 @interface CALayer (ContentsScale)
    19 - (double)contentsScale;
    20 - (void)setContentsScale:(double)scale;
    21 @end
    23 using namespace mozilla::plugins::PluginUtilsOSX;
    25 @interface CGBridgeLayer : CALayer {
    26   DrawPluginFunc mDrawFunc;
    27   void* mPluginInstance;
    28   nsIntRect mUpdateRect;
    29   BOOL mAvoidCGCrashes;
    30   CGContextRef mLastCGContext;
    31 }
    32 - (void)setDrawFunc:(DrawPluginFunc)aFunc
    33      pluginInstance:(void*)aPluginInstance
    34      avoidCGCrashes:(BOOL)aAvoidCGCrashes;
    35 - (void)updateRect:(nsIntRect)aRect;
    36 - (void)protectLastCGContext;
    38 @end
    40 // CGBitmapContextSetData() is an undocumented function present (with
    41 // the same signature) since at least OS X 10.5.  As the name suggests,
    42 // it's used to replace the "data" in a bitmap context that was
    43 // originally specified in a call to CGBitmapContextCreate() or
    44 // CGBitmapContextCreateWithData().
    45 typedef void (*CGBitmapContextSetDataFunc) (CGContextRef c,
    46                                             size_t x,
    47                                             size_t y,
    48                                             size_t width,
    49                                             size_t height,
    50                                             void* data,
    51                                             size_t bitsPerComponent,
    52                                             size_t bitsPerPixel,
    53                                             size_t bytesPerRow);
    54 CGBitmapContextSetDataFunc CGBitmapContextSetDataPtr = NULL;
    56 @implementation CGBridgeLayer
    57 - (void) updateRect:(nsIntRect)aRect
    58 {
    59    mUpdateRect.UnionRect(mUpdateRect, aRect);
    60 }
    62 - (void) setDrawFunc:(DrawPluginFunc)aFunc
    63       pluginInstance:(void*)aPluginInstance
    64       avoidCGCrashes:(BOOL)aAvoidCGCrashes
    65 {
    66   mDrawFunc = aFunc;
    67   mPluginInstance = aPluginInstance;
    68   mAvoidCGCrashes = aAvoidCGCrashes;
    69   mLastCGContext = nil;
    70 }
    72 // The Flash plugin, in very unusual circumstances, can (in CoreGraphics
    73 // mode) try to access the CGContextRef from -[CGBridgeLayer drawInContext:]
    74 // outside of any call to NPP_HandleEvent(NPCocoaEventDrawRect).  This usually
    75 // crashes the plugin process (probably because it tries to access deleted
    76 // memory).  We stop these crashes from happening by holding a reference to
    77 // the CGContextRef, and also by ensuring that it's data won't get deleted.
    78 // The CGContextRef won't "work" in this form.  But this won't cause trouble
    79 // for plugins that do things correctly (that don't access this CGContextRef
    80 // outside of the call to NPP_HandleEvent() that passes it to the plugin).
    81 // The OS may reuse this CGContextRef (it may get passed to other calls to
    82 // -[CGBridgeLayer drawInContext:]).  But before each call the OS calls
    83 // CGBitmapContextSetData() to replace its data, which undoes the changes
    84 // we make here.  See bug 804606.
    85 - (void)protectLastCGContext
    86 {
    87   if (!mAvoidCGCrashes || !mLastCGContext) {
    88     return;
    89   }
    91   static char ensuredData[128] = {0};
    93   if (!CGBitmapContextSetDataPtr) {
    94     CGBitmapContextSetDataPtr = (CGBitmapContextSetDataFunc)
    95       dlsym(RTLD_DEFAULT, "CGBitmapContextSetData");
    96   }
    98   if (CGBitmapContextSetDataPtr && (GetContextType(mLastCGContext) == CG_CONTEXT_TYPE_BITMAP)) {
    99     CGBitmapContextSetDataPtr(mLastCGContext, 0, 0, 1, 1, ensuredData, 8, 32, 64);
   100   }
   101 }
   103 - (void)drawInContext:(CGContextRef)aCGContext
   104 {
   105   ::CGContextSaveGState(aCGContext); 
   106   ::CGContextTranslateCTM(aCGContext, 0, self.bounds.size.height);
   107   ::CGContextScaleCTM(aCGContext, (CGFloat) 1, (CGFloat) -1);
   109   mUpdateRect = nsIntRect(0, 0, self.bounds.size.width, self.bounds.size.height);
   111   mDrawFunc(aCGContext, mPluginInstance, mUpdateRect);
   113   ::CGContextRestoreGState(aCGContext);
   115   if (mAvoidCGCrashes) {
   116     if (mLastCGContext) {
   117       ::CGContextRelease(mLastCGContext);
   118     }
   119     mLastCGContext = aCGContext;
   120     ::CGContextRetain(mLastCGContext);
   121   }
   123   mUpdateRect.SetEmpty();
   124 }
   126 - (void)dealloc
   127 {
   128   if (mLastCGContext) {
   129     ::CGContextRelease(mLastCGContext);
   130   }
   131   [super dealloc];
   132 }
   134 @end
   136 void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance,
   137                                                    bool aAvoidCGCrashes, double aContentsScaleFactor)
   138 {
   139   CGBridgeLayer *bridgeLayer = [[CGBridgeLayer alloc] init];
   141   // We need to make bridgeLayer behave properly when its superlayer changes
   142   // size (in nsCARenderer::SetBounds()).
   143   bridgeLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
   144   bridgeLayer.needsDisplayOnBoundsChange = YES;
   145   NSNull *nullValue = [NSNull null];
   146   NSDictionary *actions = [NSDictionary dictionaryWithObjectsAndKeys:
   147                              nullValue, @"bounds",
   148                              nullValue, @"contents",
   149                              nullValue, @"contentsRect",
   150                              nullValue, @"position",
   151                              nil];
   152   [bridgeLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]];
   154   // For reasons that aren't clear (perhaps one or more OS bugs), we can only
   155   // use full HiDPI resolution here if the tree is built with the 10.7 SDK or
   156   // up.  If we build with the 10.6 SDK, changing the contentsScale property
   157   // of bridgeLayer (even to the same value) causes it to stop working (go
   158   // blank).  This doesn't happen with objects that are members of the CALayer
   159   // class (as opposed to one of its subclasses).
   160 #if defined(MAC_OS_X_VERSION_10_7) && \
   161     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
   162   if ([bridgeLayer respondsToSelector:@selector(setContentsScale:)]) {
   163     bridgeLayer.contentsScale = aContentsScaleFactor;
   164   }
   165 #endif
   167   [bridgeLayer setDrawFunc:aFunc
   168             pluginInstance:aPluginInstance
   169             avoidCGCrashes:aAvoidCGCrashes];
   170   return bridgeLayer;
   171 }
   173 void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void *cgLayer) {
   174   CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)cgLayer;
   175   [bridgeLayer release];
   176 }
   178 void mozilla::plugins::PluginUtilsOSX::Repaint(void *caLayer, nsIntRect aRect) {
   179   CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)caLayer;
   180   [CATransaction begin];
   181   [bridgeLayer updateRect:aRect];
   182   [bridgeLayer setNeedsDisplay];
   183   [bridgeLayer displayIfNeeded];
   184   [CATransaction commit];
   185   [bridgeLayer protectLastCGContext];
   186 }
   188 @interface EventProcessor : NSObject {
   189   RemoteProcessEvents   aRemoteEvents;
   190   void                 *aPluginModule;
   191 }
   192 - (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule;
   193 - (void)onTick;
   194 @end
   196 @implementation EventProcessor
   197 - (void) onTick
   198 {
   199     aRemoteEvents(aPluginModule);
   200 }
   202 - (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule
   203 {
   204     aRemoteEvents = remoteEvents;
   205     aPluginModule = pluginModule;
   206 }
   207 @end
   209 #define EVENT_PROCESS_DELAY 0.05 // 50 ms
   211 NPError mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent) 
   212 {
   213   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   215   // Set the native cursor to the OS default (an arrow) before displaying the
   216   // context menu.  Otherwise (if the plugin has changed the cursor) it may
   217   // stay as the plugin has set it -- which means it may be invisible.  We
   218   // need to do this because we display the context menu without making the
   219   // plugin process the foreground process.  If we did, the cursor would
   220   // change to an arrow cursor automatically -- as it does in Chrome.
   221   [[NSCursor arrowCursor] set];
   223   // Create a timer to process browser events while waiting
   224   // on the menu. This prevents the browser from hanging
   225   // during the lifetime of the menu.
   226   EventProcessor* eventProcessor = [[EventProcessor alloc] init];
   227   [eventProcessor setRemoteEvents:remoteEvent pluginModule:pluginModule];
   228   NSTimer *eventTimer = [NSTimer timerWithTimeInterval:EVENT_PROCESS_DELAY
   229                              target:eventProcessor selector:@selector(onTick) 
   230                              userInfo:nil repeats:TRUE];
   231   // Use NSEventTrackingRunLoopMode otherwise the timer will
   232   // not fire during the right click menu.
   233   [[NSRunLoop currentRunLoop] addTimer:eventTimer 
   234                               forMode:NSEventTrackingRunLoopMode];
   236   NSMenu* nsmenu = reinterpret_cast<NSMenu*>(aMenu);
   237   NSPoint screen_point = ::NSMakePoint(aX, aY);
   239   [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil];
   241   [eventTimer invalidate];
   242   [eventProcessor release];
   244   return NPERR_NO_ERROR;
   246   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NPERR_GENERIC_ERROR);
   247 }
   249 void mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop()
   250 {
   251   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   252   ::CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
   253   NS_OBJC_END_TRY_ABORT_BLOCK;
   254 }
   257 #define UNDOCUMENTED_SESSION_CONSTANT ((int)-2)
   258 namespace mozilla {
   259 namespace plugins {
   260 namespace PluginUtilsOSX {
   261   static void *sApplicationASN = NULL;
   262   static void *sApplicationInfoItem = NULL;
   263 }
   264 }
   265 }
   267 bool mozilla::plugins::PluginUtilsOSX::SetProcessName(const char* aProcessName) {
   268   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   269   nsAutoreleasePool localPool;
   271   if (!aProcessName || strcmp(aProcessName, "") == 0) {
   272     return false;
   273   }
   275   NSString *currentName = [[[NSBundle mainBundle] localizedInfoDictionary] 
   276                               objectForKey:(NSString *)kCFBundleNameKey];
   278   char formattedName[1024];
   279   snprintf(formattedName, sizeof(formattedName), 
   280       "%s (%s)", [currentName UTF8String], aProcessName);
   282   aProcessName = formattedName;
   284   // This function is based on Chrome/Webkit's and relies on potentially dangerous SPI.
   285   typedef CFTypeRef (*LSGetASNType)();
   286   typedef OSStatus (*LSSetInformationItemType)(int, CFTypeRef,
   287                                                CFStringRef, 
   288                                                CFStringRef,
   289                                                CFDictionaryRef*);
   291   CFBundleRef launchServices = ::CFBundleGetBundleWithIdentifier(
   292                                           CFSTR("com.apple.LaunchServices"));
   293   if (!launchServices) {
   294     NS_WARNING("Failed to set process name: Could not open LaunchServices bundle");
   295     return false;
   296   }
   298   if (!sApplicationASN) {
   299     sApplicationASN = ::CFBundleGetFunctionPointerForName(launchServices, 
   300                                             CFSTR("_LSGetCurrentApplicationASN"));
   301   }
   303   LSGetASNType getASNFunc = reinterpret_cast<LSGetASNType>
   304                                           (sApplicationASN);
   306   if (!sApplicationInfoItem) {
   307     sApplicationInfoItem = ::CFBundleGetFunctionPointerForName(launchServices, 
   308                                             CFSTR("_LSSetApplicationInformationItem"));
   309   }
   311   LSSetInformationItemType setInformationItemFunc 
   312                                           = reinterpret_cast<LSSetInformationItemType>
   313                                           (sApplicationInfoItem);
   315   void * displayNameKeyAddr = ::CFBundleGetDataPointerForName(launchServices,
   316                                           CFSTR("_kLSDisplayNameKey"));
   318   CFStringRef displayNameKey = nil;
   319   if (displayNameKeyAddr) {
   320     displayNameKey = reinterpret_cast<CFStringRef>(*(CFStringRef*)displayNameKeyAddr);
   321   }
   323   // Rename will fail without this
   324   ProcessSerialNumber psn;
   325   if (::GetCurrentProcess(&psn) != noErr) {
   326     return false;
   327   }
   329   CFTypeRef currentAsn = getASNFunc();
   331   if (!getASNFunc || !setInformationItemFunc || 
   332       !displayNameKey || !currentAsn) {
   333     NS_WARNING("Failed to set process name: Accessing launchServices failed");
   334     return false;
   335   }
   337   CFStringRef processName = ::CFStringCreateWithCString(nil, 
   338                                                         aProcessName, 
   339                                                         kCFStringEncodingASCII);
   340   if (!processName) {
   341     NS_WARNING("Failed to set process name: Could not create CFStringRef");
   342     return false;
   343   }
   345   OSErr err = setInformationItemFunc(UNDOCUMENTED_SESSION_CONSTANT, currentAsn,
   346                                      displayNameKey, processName,
   347                                      nil); // Optional out param
   348   ::CFRelease(processName);
   349   if (err != noErr) {
   350     NS_WARNING("Failed to set process name: LSSetInformationItemType err");
   351     return false;
   352   }
   354   return true;
   355   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
   356 }
   358 namespace mozilla {
   359 namespace plugins {
   360 namespace PluginUtilsOSX {
   362 size_t nsDoubleBufferCARenderer::GetFrontSurfaceWidth() {
   363   if (!HasFrontSurface()) {
   364     return 0;
   365   }
   367   return mFrontSurface->GetWidth();
   368 }
   370 size_t nsDoubleBufferCARenderer::GetFrontSurfaceHeight() {
   371   if (!HasFrontSurface()) {
   372     return 0;
   373   }
   375   return mFrontSurface->GetHeight();
   376 }
   378 double nsDoubleBufferCARenderer::GetFrontSurfaceContentsScaleFactor() {
   379   if (!HasFrontSurface()) {
   380     return 1.0;
   381   }
   383   return mFrontSurface->GetContentsScaleFactor();
   384 }
   386 size_t nsDoubleBufferCARenderer::GetBackSurfaceWidth() {
   387   if (!HasBackSurface()) {
   388     return 0;
   389   }
   391   return mBackSurface->GetWidth();
   392 }
   394 size_t nsDoubleBufferCARenderer::GetBackSurfaceHeight() {
   395   if (!HasBackSurface()) {
   396     return 0;
   397   }
   399   return mBackSurface->GetHeight();
   400 }
   402 double nsDoubleBufferCARenderer::GetBackSurfaceContentsScaleFactor() {
   403   if (!HasBackSurface()) {
   404     return 1.0;
   405   }
   407   return mBackSurface->GetContentsScaleFactor();
   408 }
   410 IOSurfaceID nsDoubleBufferCARenderer::GetFrontSurfaceID() {
   411   if (!HasFrontSurface()) {
   412     return 0;
   413   }
   415   return mFrontSurface->GetIOSurfaceID();
   416 }
   418 bool nsDoubleBufferCARenderer::HasBackSurface() {
   419   return !!mBackSurface;
   420 }
   422 bool nsDoubleBufferCARenderer::HasFrontSurface() {
   423   return !!mFrontSurface;
   424 }
   426 bool nsDoubleBufferCARenderer::HasCALayer() {
   427   return !!mCALayer;
   428 }
   430 void nsDoubleBufferCARenderer::SetCALayer(void *aCALayer) {
   431   mCALayer = aCALayer;
   432 }
   434 bool nsDoubleBufferCARenderer::InitFrontSurface(size_t aWidth, size_t aHeight,
   435                                                 double aContentsScaleFactor,
   436                                                 AllowOfflineRendererEnum aAllowOfflineRenderer) {
   437   if (!mCALayer) {
   438     return false;
   439   }
   441   mContentsScaleFactor = aContentsScaleFactor;
   442   mFrontSurface = MacIOSurface::CreateIOSurface(aWidth, aHeight, mContentsScaleFactor);
   443   if (!mFrontSurface) {
   444     mCARenderer = nullptr;
   445     return false;
   446   }
   448   if (!mCARenderer) {
   449     mCARenderer = new nsCARenderer();
   450     if (!mCARenderer) {
   451       mFrontSurface = nullptr;
   452       return false;
   453     }
   455     mCARenderer->AttachIOSurface(mFrontSurface);
   457     nsresult result = mCARenderer->SetupRenderer(mCALayer,
   458                         mFrontSurface->GetWidth(),
   459                         mFrontSurface->GetHeight(),
   460                         mContentsScaleFactor,
   461                         aAllowOfflineRenderer);
   463     if (result != NS_OK) {
   464       mCARenderer = nullptr;
   465       mFrontSurface = nullptr;
   466       return false;
   467     }
   468   } else {
   469     mCARenderer->AttachIOSurface(mFrontSurface);
   470   }
   472   return true;
   473 }
   475 void nsDoubleBufferCARenderer::Render() {
   476   if (!HasFrontSurface() || !mCARenderer) {
   477     return;
   478   }
   480   mCARenderer->Render(GetFrontSurfaceWidth(), GetFrontSurfaceHeight(),
   481                       mContentsScaleFactor, nullptr);
   482 }
   484 void nsDoubleBufferCARenderer::SwapSurfaces() {
   485   RefPtr<MacIOSurface> prevFrontSurface = mFrontSurface;
   486   mFrontSurface = mBackSurface;
   487   mBackSurface = prevFrontSurface;
   489   if (mFrontSurface) {
   490     mCARenderer->AttachIOSurface(mFrontSurface);
   491   }
   492 }
   494 void nsDoubleBufferCARenderer::ClearFrontSurface() {
   495   mFrontSurface = nullptr;
   496   if (!mFrontSurface && !mBackSurface) {
   497     mCARenderer = nullptr;
   498   }
   499 }
   501 void nsDoubleBufferCARenderer::ClearBackSurface() {
   502   mBackSurface = nullptr;
   503   if (!mFrontSurface && !mBackSurface) {
   504     mCARenderer = nullptr;
   505   }
   506 }
   508 } //PluginUtilsOSX
   509 } //plugins
   510 } //mozilla

mercurial