Thu, 22 Jan 2015 13:21:57 +0100
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