|
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/. */ |
|
6 |
|
7 #include <dlfcn.h> |
|
8 #import <AppKit/AppKit.h> |
|
9 #import <QuartzCore/QuartzCore.h> |
|
10 #include "PluginUtilsOSX.h" |
|
11 |
|
12 // Remove definitions for try/catch interfering with ObjCException macros. |
|
13 #include "nsObjCExceptions.h" |
|
14 #include "nsCocoaUtils.h" |
|
15 |
|
16 #include "nsDebug.h" |
|
17 |
|
18 @interface CALayer (ContentsScale) |
|
19 - (double)contentsScale; |
|
20 - (void)setContentsScale:(double)scale; |
|
21 @end |
|
22 |
|
23 using namespace mozilla::plugins::PluginUtilsOSX; |
|
24 |
|
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; |
|
37 |
|
38 @end |
|
39 |
|
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; |
|
55 |
|
56 @implementation CGBridgeLayer |
|
57 - (void) updateRect:(nsIntRect)aRect |
|
58 { |
|
59 mUpdateRect.UnionRect(mUpdateRect, aRect); |
|
60 } |
|
61 |
|
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 } |
|
71 |
|
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 } |
|
90 |
|
91 static char ensuredData[128] = {0}; |
|
92 |
|
93 if (!CGBitmapContextSetDataPtr) { |
|
94 CGBitmapContextSetDataPtr = (CGBitmapContextSetDataFunc) |
|
95 dlsym(RTLD_DEFAULT, "CGBitmapContextSetData"); |
|
96 } |
|
97 |
|
98 if (CGBitmapContextSetDataPtr && (GetContextType(mLastCGContext) == CG_CONTEXT_TYPE_BITMAP)) { |
|
99 CGBitmapContextSetDataPtr(mLastCGContext, 0, 0, 1, 1, ensuredData, 8, 32, 64); |
|
100 } |
|
101 } |
|
102 |
|
103 - (void)drawInContext:(CGContextRef)aCGContext |
|
104 { |
|
105 ::CGContextSaveGState(aCGContext); |
|
106 ::CGContextTranslateCTM(aCGContext, 0, self.bounds.size.height); |
|
107 ::CGContextScaleCTM(aCGContext, (CGFloat) 1, (CGFloat) -1); |
|
108 |
|
109 mUpdateRect = nsIntRect(0, 0, self.bounds.size.width, self.bounds.size.height); |
|
110 |
|
111 mDrawFunc(aCGContext, mPluginInstance, mUpdateRect); |
|
112 |
|
113 ::CGContextRestoreGState(aCGContext); |
|
114 |
|
115 if (mAvoidCGCrashes) { |
|
116 if (mLastCGContext) { |
|
117 ::CGContextRelease(mLastCGContext); |
|
118 } |
|
119 mLastCGContext = aCGContext; |
|
120 ::CGContextRetain(mLastCGContext); |
|
121 } |
|
122 |
|
123 mUpdateRect.SetEmpty(); |
|
124 } |
|
125 |
|
126 - (void)dealloc |
|
127 { |
|
128 if (mLastCGContext) { |
|
129 ::CGContextRelease(mLastCGContext); |
|
130 } |
|
131 [super dealloc]; |
|
132 } |
|
133 |
|
134 @end |
|
135 |
|
136 void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, |
|
137 bool aAvoidCGCrashes, double aContentsScaleFactor) |
|
138 { |
|
139 CGBridgeLayer *bridgeLayer = [[CGBridgeLayer alloc] init]; |
|
140 |
|
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"]]; |
|
153 |
|
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 |
|
166 |
|
167 [bridgeLayer setDrawFunc:aFunc |
|
168 pluginInstance:aPluginInstance |
|
169 avoidCGCrashes:aAvoidCGCrashes]; |
|
170 return bridgeLayer; |
|
171 } |
|
172 |
|
173 void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void *cgLayer) { |
|
174 CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)cgLayer; |
|
175 [bridgeLayer release]; |
|
176 } |
|
177 |
|
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 } |
|
187 |
|
188 @interface EventProcessor : NSObject { |
|
189 RemoteProcessEvents aRemoteEvents; |
|
190 void *aPluginModule; |
|
191 } |
|
192 - (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule; |
|
193 - (void)onTick; |
|
194 @end |
|
195 |
|
196 @implementation EventProcessor |
|
197 - (void) onTick |
|
198 { |
|
199 aRemoteEvents(aPluginModule); |
|
200 } |
|
201 |
|
202 - (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule |
|
203 { |
|
204 aRemoteEvents = remoteEvents; |
|
205 aPluginModule = pluginModule; |
|
206 } |
|
207 @end |
|
208 |
|
209 #define EVENT_PROCESS_DELAY 0.05 // 50 ms |
|
210 |
|
211 NPError mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent) |
|
212 { |
|
213 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
214 |
|
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]; |
|
222 |
|
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]; |
|
235 |
|
236 NSMenu* nsmenu = reinterpret_cast<NSMenu*>(aMenu); |
|
237 NSPoint screen_point = ::NSMakePoint(aX, aY); |
|
238 |
|
239 [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil]; |
|
240 |
|
241 [eventTimer invalidate]; |
|
242 [eventProcessor release]; |
|
243 |
|
244 return NPERR_NO_ERROR; |
|
245 |
|
246 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NPERR_GENERIC_ERROR); |
|
247 } |
|
248 |
|
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 } |
|
255 |
|
256 |
|
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 } |
|
266 |
|
267 bool mozilla::plugins::PluginUtilsOSX::SetProcessName(const char* aProcessName) { |
|
268 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
269 nsAutoreleasePool localPool; |
|
270 |
|
271 if (!aProcessName || strcmp(aProcessName, "") == 0) { |
|
272 return false; |
|
273 } |
|
274 |
|
275 NSString *currentName = [[[NSBundle mainBundle] localizedInfoDictionary] |
|
276 objectForKey:(NSString *)kCFBundleNameKey]; |
|
277 |
|
278 char formattedName[1024]; |
|
279 snprintf(formattedName, sizeof(formattedName), |
|
280 "%s (%s)", [currentName UTF8String], aProcessName); |
|
281 |
|
282 aProcessName = formattedName; |
|
283 |
|
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*); |
|
290 |
|
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 } |
|
297 |
|
298 if (!sApplicationASN) { |
|
299 sApplicationASN = ::CFBundleGetFunctionPointerForName(launchServices, |
|
300 CFSTR("_LSGetCurrentApplicationASN")); |
|
301 } |
|
302 |
|
303 LSGetASNType getASNFunc = reinterpret_cast<LSGetASNType> |
|
304 (sApplicationASN); |
|
305 |
|
306 if (!sApplicationInfoItem) { |
|
307 sApplicationInfoItem = ::CFBundleGetFunctionPointerForName(launchServices, |
|
308 CFSTR("_LSSetApplicationInformationItem")); |
|
309 } |
|
310 |
|
311 LSSetInformationItemType setInformationItemFunc |
|
312 = reinterpret_cast<LSSetInformationItemType> |
|
313 (sApplicationInfoItem); |
|
314 |
|
315 void * displayNameKeyAddr = ::CFBundleGetDataPointerForName(launchServices, |
|
316 CFSTR("_kLSDisplayNameKey")); |
|
317 |
|
318 CFStringRef displayNameKey = nil; |
|
319 if (displayNameKeyAddr) { |
|
320 displayNameKey = reinterpret_cast<CFStringRef>(*(CFStringRef*)displayNameKeyAddr); |
|
321 } |
|
322 |
|
323 // Rename will fail without this |
|
324 ProcessSerialNumber psn; |
|
325 if (::GetCurrentProcess(&psn) != noErr) { |
|
326 return false; |
|
327 } |
|
328 |
|
329 CFTypeRef currentAsn = getASNFunc(); |
|
330 |
|
331 if (!getASNFunc || !setInformationItemFunc || |
|
332 !displayNameKey || !currentAsn) { |
|
333 NS_WARNING("Failed to set process name: Accessing launchServices failed"); |
|
334 return false; |
|
335 } |
|
336 |
|
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 } |
|
344 |
|
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 } |
|
353 |
|
354 return true; |
|
355 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); |
|
356 } |
|
357 |
|
358 namespace mozilla { |
|
359 namespace plugins { |
|
360 namespace PluginUtilsOSX { |
|
361 |
|
362 size_t nsDoubleBufferCARenderer::GetFrontSurfaceWidth() { |
|
363 if (!HasFrontSurface()) { |
|
364 return 0; |
|
365 } |
|
366 |
|
367 return mFrontSurface->GetWidth(); |
|
368 } |
|
369 |
|
370 size_t nsDoubleBufferCARenderer::GetFrontSurfaceHeight() { |
|
371 if (!HasFrontSurface()) { |
|
372 return 0; |
|
373 } |
|
374 |
|
375 return mFrontSurface->GetHeight(); |
|
376 } |
|
377 |
|
378 double nsDoubleBufferCARenderer::GetFrontSurfaceContentsScaleFactor() { |
|
379 if (!HasFrontSurface()) { |
|
380 return 1.0; |
|
381 } |
|
382 |
|
383 return mFrontSurface->GetContentsScaleFactor(); |
|
384 } |
|
385 |
|
386 size_t nsDoubleBufferCARenderer::GetBackSurfaceWidth() { |
|
387 if (!HasBackSurface()) { |
|
388 return 0; |
|
389 } |
|
390 |
|
391 return mBackSurface->GetWidth(); |
|
392 } |
|
393 |
|
394 size_t nsDoubleBufferCARenderer::GetBackSurfaceHeight() { |
|
395 if (!HasBackSurface()) { |
|
396 return 0; |
|
397 } |
|
398 |
|
399 return mBackSurface->GetHeight(); |
|
400 } |
|
401 |
|
402 double nsDoubleBufferCARenderer::GetBackSurfaceContentsScaleFactor() { |
|
403 if (!HasBackSurface()) { |
|
404 return 1.0; |
|
405 } |
|
406 |
|
407 return mBackSurface->GetContentsScaleFactor(); |
|
408 } |
|
409 |
|
410 IOSurfaceID nsDoubleBufferCARenderer::GetFrontSurfaceID() { |
|
411 if (!HasFrontSurface()) { |
|
412 return 0; |
|
413 } |
|
414 |
|
415 return mFrontSurface->GetIOSurfaceID(); |
|
416 } |
|
417 |
|
418 bool nsDoubleBufferCARenderer::HasBackSurface() { |
|
419 return !!mBackSurface; |
|
420 } |
|
421 |
|
422 bool nsDoubleBufferCARenderer::HasFrontSurface() { |
|
423 return !!mFrontSurface; |
|
424 } |
|
425 |
|
426 bool nsDoubleBufferCARenderer::HasCALayer() { |
|
427 return !!mCALayer; |
|
428 } |
|
429 |
|
430 void nsDoubleBufferCARenderer::SetCALayer(void *aCALayer) { |
|
431 mCALayer = aCALayer; |
|
432 } |
|
433 |
|
434 bool nsDoubleBufferCARenderer::InitFrontSurface(size_t aWidth, size_t aHeight, |
|
435 double aContentsScaleFactor, |
|
436 AllowOfflineRendererEnum aAllowOfflineRenderer) { |
|
437 if (!mCALayer) { |
|
438 return false; |
|
439 } |
|
440 |
|
441 mContentsScaleFactor = aContentsScaleFactor; |
|
442 mFrontSurface = MacIOSurface::CreateIOSurface(aWidth, aHeight, mContentsScaleFactor); |
|
443 if (!mFrontSurface) { |
|
444 mCARenderer = nullptr; |
|
445 return false; |
|
446 } |
|
447 |
|
448 if (!mCARenderer) { |
|
449 mCARenderer = new nsCARenderer(); |
|
450 if (!mCARenderer) { |
|
451 mFrontSurface = nullptr; |
|
452 return false; |
|
453 } |
|
454 |
|
455 mCARenderer->AttachIOSurface(mFrontSurface); |
|
456 |
|
457 nsresult result = mCARenderer->SetupRenderer(mCALayer, |
|
458 mFrontSurface->GetWidth(), |
|
459 mFrontSurface->GetHeight(), |
|
460 mContentsScaleFactor, |
|
461 aAllowOfflineRenderer); |
|
462 |
|
463 if (result != NS_OK) { |
|
464 mCARenderer = nullptr; |
|
465 mFrontSurface = nullptr; |
|
466 return false; |
|
467 } |
|
468 } else { |
|
469 mCARenderer->AttachIOSurface(mFrontSurface); |
|
470 } |
|
471 |
|
472 return true; |
|
473 } |
|
474 |
|
475 void nsDoubleBufferCARenderer::Render() { |
|
476 if (!HasFrontSurface() || !mCARenderer) { |
|
477 return; |
|
478 } |
|
479 |
|
480 mCARenderer->Render(GetFrontSurfaceWidth(), GetFrontSurfaceHeight(), |
|
481 mContentsScaleFactor, nullptr); |
|
482 } |
|
483 |
|
484 void nsDoubleBufferCARenderer::SwapSurfaces() { |
|
485 RefPtr<MacIOSurface> prevFrontSurface = mFrontSurface; |
|
486 mFrontSurface = mBackSurface; |
|
487 mBackSurface = prevFrontSurface; |
|
488 |
|
489 if (mFrontSurface) { |
|
490 mCARenderer->AttachIOSurface(mFrontSurface); |
|
491 } |
|
492 } |
|
493 |
|
494 void nsDoubleBufferCARenderer::ClearFrontSurface() { |
|
495 mFrontSurface = nullptr; |
|
496 if (!mFrontSurface && !mBackSurface) { |
|
497 mCARenderer = nullptr; |
|
498 } |
|
499 } |
|
500 |
|
501 void nsDoubleBufferCARenderer::ClearBackSurface() { |
|
502 mBackSurface = nullptr; |
|
503 if (!mFrontSurface && !mBackSurface) { |
|
504 mCARenderer = nullptr; |
|
505 } |
|
506 } |
|
507 |
|
508 } //PluginUtilsOSX |
|
509 } //plugins |
|
510 } //mozilla |
|
511 |