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 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 // * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "base/basictypes.h"
32 #include "nsCocoaUtils.h"
33 #include "PluginModuleChild.h"
34 #include "nsDebug.h"
35 #include "PluginInterposeOSX.h"
36 #include <set>
37 #import <AppKit/AppKit.h>
38 #import <objc/runtime.h>
39 #import <Carbon/Carbon.h>
41 using mozilla::plugins::PluginModuleChild;
42 using mozilla::plugins::AssertPluginThread;
44 namespace mac_plugin_interposing {
46 int32_t NSCursorInfo::mNativeCursorsSupported = -1;
48 // This constructor may be called from the browser process or the plugin
49 // process.
50 NSCursorInfo::NSCursorInfo()
51 : mType(TypeArrow)
52 , mHotSpot(nsPoint(0, 0))
53 , mCustomImageData(NULL)
54 , mCustomImageDataLength(0)
55 {
56 }
58 NSCursorInfo::NSCursorInfo(NSCursor* aCursor)
59 : mType(TypeArrow)
60 , mHotSpot(nsPoint(0, 0))
61 , mCustomImageData(NULL)
62 , mCustomImageDataLength(0)
63 {
64 // This constructor is only ever called from the plugin process, so the
65 // following is safe.
66 if (!GetNativeCursorsSupported()) {
67 return;
68 }
70 NSPoint hotSpotCocoa = [aCursor hotSpot];
71 mHotSpot = nsPoint(hotSpotCocoa.x, hotSpotCocoa.y);
73 Class nsCursorClass = [NSCursor class];
74 if ([aCursor isEqual:[NSCursor arrowCursor]]) {
75 mType = TypeArrow;
76 } else if ([aCursor isEqual:[NSCursor closedHandCursor]]) {
77 mType = TypeClosedHand;
78 } else if ([aCursor isEqual:[NSCursor crosshairCursor]]) {
79 mType = TypeCrosshair;
80 } else if ([aCursor isEqual:[NSCursor disappearingItemCursor]]) {
81 mType = TypeDisappearingItem;
82 } else if ([aCursor isEqual:[NSCursor IBeamCursor]]) {
83 mType = TypeIBeam;
84 } else if ([aCursor isEqual:[NSCursor openHandCursor]]) {
85 mType = TypeOpenHand;
86 } else if ([aCursor isEqual:[NSCursor pointingHandCursor]]) {
87 mType = TypePointingHand;
88 } else if ([aCursor isEqual:[NSCursor resizeDownCursor]]) {
89 mType = TypeResizeDown;
90 } else if ([aCursor isEqual:[NSCursor resizeLeftCursor]]) {
91 mType = TypeResizeLeft;
92 } else if ([aCursor isEqual:[NSCursor resizeLeftRightCursor]]) {
93 mType = TypeResizeLeftRight;
94 } else if ([aCursor isEqual:[NSCursor resizeRightCursor]]) {
95 mType = TypeResizeRight;
96 } else if ([aCursor isEqual:[NSCursor resizeUpCursor]]) {
97 mType = TypeResizeUp;
98 } else if ([aCursor isEqual:[NSCursor resizeUpDownCursor]]) {
99 mType = TypeResizeUpDown;
100 // The following cursor types are only supported on OS X 10.6 and up.
101 } else if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)] &&
102 [aCursor isEqual:[nsCursorClass performSelector:@selector(contextualMenuCursor)]]) {
103 mType = TypeContextualMenu;
104 } else if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)] &&
105 [aCursor isEqual:[nsCursorClass performSelector:@selector(dragCopyCursor)]]) {
106 mType = TypeDragCopy;
107 } else if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)] &&
108 [aCursor isEqual:[nsCursorClass performSelector:@selector(dragLinkCursor)]]) {
109 mType = TypeDragLink;
110 } else if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)] &&
111 [aCursor isEqual:[nsCursorClass performSelector:@selector(operationNotAllowedCursor)]]) {
112 mType = TypeNotAllowed;
113 } else {
114 NSImage* image = [aCursor image];
115 NSArray* reps = image ? [image representations] : nil;
116 NSUInteger repsCount = reps ? [reps count] : 0;
117 if (!repsCount) {
118 // If we have a custom cursor with no image representations, assume we
119 // need a transparent cursor.
120 mType = TypeTransparent;
121 } else {
122 CGImageRef cgImage = nil;
123 // XXX We don't know how to deal with a cursor that doesn't have a
124 // bitmap image representation. For now we fall back to an arrow
125 // cursor.
126 for (NSUInteger i = 0; i < repsCount; ++i) {
127 id rep = [reps objectAtIndex:i];
128 if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
129 cgImage = [(NSBitmapImageRep*)rep CGImage];
130 break;
131 }
132 }
133 if (cgImage) {
134 CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0);
135 if (data) {
136 CGImageDestinationRef dest = ::CGImageDestinationCreateWithData(data,
137 kUTTypePNG,
138 1,
139 NULL);
140 if (dest) {
141 ::CGImageDestinationAddImage(dest, cgImage, NULL);
142 if (::CGImageDestinationFinalize(dest)) {
143 uint32_t dataLength = (uint32_t) ::CFDataGetLength(data);
144 mCustomImageData = (uint8_t*) moz_xmalloc(dataLength);
145 ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData);
146 mCustomImageDataLength = dataLength;
147 mType = TypeCustom;
148 }
149 ::CFRelease(dest);
150 }
151 ::CFRelease(data);
152 }
153 }
154 if (!mCustomImageData) {
155 mType = TypeArrow;
156 }
157 }
158 }
159 }
161 NSCursorInfo::NSCursorInfo(const Cursor* aCursor)
162 : mType(TypeArrow)
163 , mHotSpot(nsPoint(0, 0))
164 , mCustomImageData(NULL)
165 , mCustomImageDataLength(0)
166 {
167 // This constructor is only ever called from the plugin process, so the
168 // following is safe.
169 if (!GetNativeCursorsSupported()) {
170 return;
171 }
173 mHotSpot = nsPoint(aCursor->hotSpot.h, aCursor->hotSpot.v);
175 int width = 16, height = 16;
176 int bytesPerPixel = 4;
177 int rowBytes = width * bytesPerPixel;
178 int bitmapSize = height * rowBytes;
180 bool isTransparent = true;
182 uint8_t* bitmap = (uint8_t*) moz_xmalloc(bitmapSize);
183 // The way we create 'bitmap' is largely "borrowed" from Chrome's
184 // WebCursor::InitFromCursor().
185 for (int y = 0; y < height; ++y) {
186 unsigned short data = aCursor->data[y];
187 unsigned short mask = aCursor->mask[y];
188 // Change 'data' and 'mask' from big-endian to little-endian, but output
189 // big-endian data below.
190 data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF);
191 mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF);
192 // It'd be nice to use a gray-scale bitmap. But
193 // CGBitmapContextCreateImage() (used below) won't work with one that also
194 // has alpha values.
195 for (int x = 0; x < width; ++x) {
196 int offset = (y * rowBytes) + (x * bytesPerPixel);
197 // Color value
198 if (data & 0x8000) {
199 bitmap[offset] = 0x0;
200 bitmap[offset + 1] = 0x0;
201 bitmap[offset + 2] = 0x0;
202 } else {
203 bitmap[offset] = 0xFF;
204 bitmap[offset + 1] = 0xFF;
205 bitmap[offset + 2] = 0xFF;
206 }
207 // Mask value
208 if (mask & 0x8000) {
209 bitmap[offset + 3] = 0xFF;
210 isTransparent = false;
211 } else {
212 bitmap[offset + 3] = 0x0;
213 }
214 data <<= 1;
215 mask <<= 1;
216 }
217 }
219 if (isTransparent) {
220 // If aCursor is transparent, we don't need to serialize custom cursor
221 // data over IPC.
222 mType = TypeTransparent;
223 } else {
224 CGColorSpaceRef color = ::CGColorSpaceCreateDeviceRGB();
225 if (color) {
226 CGContextRef context =
227 ::CGBitmapContextCreate(bitmap,
228 width,
229 height,
230 8,
231 rowBytes,
232 color,
233 kCGImageAlphaPremultipliedLast |
234 kCGBitmapByteOrder32Big);
235 if (context) {
236 CGImageRef image = ::CGBitmapContextCreateImage(context);
237 if (image) {
238 ::CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0);
239 if (data) {
240 CGImageDestinationRef dest =
241 ::CGImageDestinationCreateWithData(data,
242 kUTTypePNG,
243 1,
244 NULL);
245 if (dest) {
246 ::CGImageDestinationAddImage(dest, image, NULL);
247 if (::CGImageDestinationFinalize(dest)) {
248 uint32_t dataLength = (uint32_t) ::CFDataGetLength(data);
249 mCustomImageData = (uint8_t*) moz_xmalloc(dataLength);
250 ::CFDataGetBytes(data,
251 ::CFRangeMake(0, dataLength),
252 mCustomImageData);
253 mCustomImageDataLength = dataLength;
254 mType = TypeCustom;
255 }
256 ::CFRelease(dest);
257 }
258 ::CFRelease(data);
259 }
260 ::CGImageRelease(image);
261 }
262 ::CGContextRelease(context);
263 }
264 ::CGColorSpaceRelease(color);
265 }
266 }
268 moz_free(bitmap);
269 }
271 NSCursorInfo::~NSCursorInfo()
272 {
273 if (mCustomImageData) {
274 moz_free(mCustomImageData);
275 }
276 }
278 NSCursor* NSCursorInfo::GetNSCursor() const
279 {
280 NSCursor* retval = nil;
282 Class nsCursorClass = [NSCursor class];
283 switch(mType) {
284 case TypeArrow:
285 retval = [NSCursor arrowCursor];
286 break;
287 case TypeClosedHand:
288 retval = [NSCursor closedHandCursor];
289 break;
290 case TypeCrosshair:
291 retval = [NSCursor crosshairCursor];
292 break;
293 case TypeDisappearingItem:
294 retval = [NSCursor disappearingItemCursor];
295 break;
296 case TypeIBeam:
297 retval = [NSCursor IBeamCursor];
298 break;
299 case TypeOpenHand:
300 retval = [NSCursor openHandCursor];
301 break;
302 case TypePointingHand:
303 retval = [NSCursor pointingHandCursor];
304 break;
305 case TypeResizeDown:
306 retval = [NSCursor resizeDownCursor];
307 break;
308 case TypeResizeLeft:
309 retval = [NSCursor resizeLeftCursor];
310 break;
311 case TypeResizeLeftRight:
312 retval = [NSCursor resizeLeftRightCursor];
313 break;
314 case TypeResizeRight:
315 retval = [NSCursor resizeRightCursor];
316 break;
317 case TypeResizeUp:
318 retval = [NSCursor resizeUpCursor];
319 break;
320 case TypeResizeUpDown:
321 retval = [NSCursor resizeUpDownCursor];
322 break;
323 // The following four cursor types are only supported on OS X 10.6 and up.
324 case TypeContextualMenu: {
325 if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)]) {
326 retval = [nsCursorClass performSelector:@selector(contextualMenuCursor)];
327 }
328 break;
329 }
330 case TypeDragCopy: {
331 if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)]) {
332 retval = [nsCursorClass performSelector:@selector(dragCopyCursor)];
333 }
334 break;
335 }
336 case TypeDragLink: {
337 if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)]) {
338 retval = [nsCursorClass performSelector:@selector(dragLinkCursor)];
339 }
340 break;
341 }
342 case TypeNotAllowed: {
343 if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)]) {
344 retval = [nsCursorClass performSelector:@selector(operationNotAllowedCursor)];
345 }
346 break;
347 }
348 case TypeTransparent:
349 retval = GetTransparentCursor();
350 break;
351 default:
352 break;
353 }
355 if (!retval && mCustomImageData && mCustomImageDataLength) {
356 CGDataProviderRef provider = ::CGDataProviderCreateWithData(NULL,
357 (const void*)mCustomImageData,
358 mCustomImageDataLength,
359 NULL);
360 if (provider) {
361 CGImageRef cgImage = ::CGImageCreateWithPNGDataProvider(provider,
362 NULL,
363 false,
364 kCGRenderingIntentDefault);
365 if (cgImage) {
366 NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
367 if (rep) {
368 NSImage* image = [[NSImage alloc] init];
369 if (image) {
370 [image addRepresentation:rep];
371 retval = [[[NSCursor alloc] initWithImage:image
372 hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)]
373 autorelease];
374 [image release];
375 }
376 [rep release];
377 }
378 ::CGImageRelease(cgImage);
379 }
380 ::CFRelease(provider);
381 }
382 }
384 // Fall back to an arrow cursor if need be.
385 if (!retval) {
386 retval = [NSCursor arrowCursor];
387 }
389 return retval;
390 }
392 // Get a transparent cursor with the appropriate hot spot. We need one if
393 // (for example) we have a custom cursor with no image data.
394 NSCursor* NSCursorInfo::GetTransparentCursor() const
395 {
396 NSCursor* retval = nil;
398 int width = 16, height = 16;
399 int bytesPerPixel = 2;
400 int rowBytes = width * bytesPerPixel;
401 int dataSize = height * rowBytes;
403 uint8_t* data = (uint8_t*) moz_xmalloc(dataSize);
404 for (int y = 0; y < height; ++y) {
405 for (int x = 0; x < width; ++x) {
406 int offset = (y * rowBytes) + (x * bytesPerPixel);
407 data[offset] = 0x7E; // Arbitrary gray-scale value
408 data[offset + 1] = 0; // Alpha value to make us transparent
409 }
410 }
412 NSBitmapImageRep* imageRep =
413 [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
414 pixelsWide:width
415 pixelsHigh:height
416 bitsPerSample:8
417 samplesPerPixel:2
418 hasAlpha:YES
419 isPlanar:NO
420 colorSpaceName:NSCalibratedWhiteColorSpace
421 bytesPerRow:rowBytes
422 bitsPerPixel:16]
423 autorelease];
424 if (imageRep) {
425 uint8_t* repDataPtr = [imageRep bitmapData];
426 if (repDataPtr) {
427 memcpy(repDataPtr, data, dataSize);
428 NSImage *image =
429 [[[NSImage alloc] initWithSize:NSMakeSize(width, height)]
430 autorelease];
431 if (image) {
432 [image addRepresentation:imageRep];
433 retval =
434 [[[NSCursor alloc] initWithImage:image
435 hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)]
436 autorelease];
437 }
438 }
439 }
441 moz_free(data);
443 // Fall back to an arrow cursor if (for some reason) the above code failed.
444 if (!retval) {
445 retval = [NSCursor arrowCursor];
446 }
448 return retval;
449 }
451 NSCursorInfo::Type NSCursorInfo::GetType() const
452 {
453 return mType;
454 }
456 const char* NSCursorInfo::GetTypeName() const
457 {
458 switch(mType) {
459 case TypeCustom:
460 return "TypeCustom";
461 case TypeArrow:
462 return "TypeArrow";
463 case TypeClosedHand:
464 return "TypeClosedHand";
465 case TypeContextualMenu:
466 return "TypeContextualMenu";
467 case TypeCrosshair:
468 return "TypeCrosshair";
469 case TypeDisappearingItem:
470 return "TypeDisappearingItem";
471 case TypeDragCopy:
472 return "TypeDragCopy";
473 case TypeDragLink:
474 return "TypeDragLink";
475 case TypeIBeam:
476 return "TypeIBeam";
477 case TypeNotAllowed:
478 return "TypeNotAllowed";
479 case TypeOpenHand:
480 return "TypeOpenHand";
481 case TypePointingHand:
482 return "TypePointingHand";
483 case TypeResizeDown:
484 return "TypeResizeDown";
485 case TypeResizeLeft:
486 return "TypeResizeLeft";
487 case TypeResizeLeftRight:
488 return "TypeResizeLeftRight";
489 case TypeResizeRight:
490 return "TypeResizeRight";
491 case TypeResizeUp:
492 return "TypeResizeUp";
493 case TypeResizeUpDown:
494 return "TypeResizeUpDown";
495 case TypeTransparent:
496 return "TypeTransparent";
497 default:
498 break;
499 }
500 return "TypeUnknown";
501 }
503 nsPoint NSCursorInfo::GetHotSpot() const
504 {
505 return mHotSpot;
506 }
508 uint8_t* NSCursorInfo::GetCustomImageData() const
509 {
510 return mCustomImageData;
511 }
513 uint32_t NSCursorInfo::GetCustomImageDataLength() const
514 {
515 return mCustomImageDataLength;
516 }
518 void NSCursorInfo::SetType(Type aType)
519 {
520 mType = aType;
521 }
523 void NSCursorInfo::SetHotSpot(nsPoint aHotSpot)
524 {
525 mHotSpot = aHotSpot;
526 }
528 void NSCursorInfo::SetCustomImageData(uint8_t* aData, uint32_t aDataLength)
529 {
530 if (mCustomImageData) {
531 moz_free(mCustomImageData);
532 }
533 if (aDataLength) {
534 mCustomImageData = (uint8_t*) moz_xmalloc(aDataLength);
535 memcpy(mCustomImageData, aData, aDataLength);
536 } else {
537 mCustomImageData = NULL;
538 }
539 mCustomImageDataLength = aDataLength;
540 }
542 // This should never be called from the browser process -- only from the
543 // plugin process.
544 bool NSCursorInfo::GetNativeCursorsSupported()
545 {
546 if (mNativeCursorsSupported == -1) {
547 AssertPluginThread();
548 PluginModuleChild *pmc = PluginModuleChild::current();
549 if (pmc) {
550 bool result = pmc->GetNativeCursorsSupported();
551 if (result) {
552 mNativeCursorsSupported = 1;
553 } else {
554 mNativeCursorsSupported = 0;
555 }
556 }
557 }
558 return (mNativeCursorsSupported == 1);
559 }
561 } // namespace mac_plugin_interposing
563 namespace mac_plugin_interposing {
564 namespace parent {
566 // Tracks plugin windows currently visible.
567 std::set<uint32_t> plugin_visible_windows_set_;
568 // Tracks full screen windows currently visible.
569 std::set<uint32_t> plugin_fullscreen_windows_set_;
570 // Tracks modal windows currently visible.
571 std::set<uint32_t> plugin_modal_windows_set_;
573 void OnPluginShowWindow(uint32_t window_id,
574 CGRect window_bounds,
575 bool modal) {
576 plugin_visible_windows_set_.insert(window_id);
578 if (modal)
579 plugin_modal_windows_set_.insert(window_id);
581 CGRect main_display_bounds = ::CGDisplayBounds(CGMainDisplayID());
583 if (CGRectEqualToRect(window_bounds, main_display_bounds) &&
584 (plugin_fullscreen_windows_set_.find(window_id) ==
585 plugin_fullscreen_windows_set_.end())) {
586 plugin_fullscreen_windows_set_.insert(window_id);
588 nsCocoaUtils::HideOSChromeOnScreen(TRUE, [[NSScreen screens] objectAtIndex:0]);
589 }
590 }
592 static void ActivateProcess(pid_t pid) {
593 ProcessSerialNumber process;
594 OSStatus status = ::GetProcessForPID(pid, &process);
596 if (status == noErr) {
597 SetFrontProcess(&process);
598 } else {
599 NS_WARNING("Unable to get process for pid.");
600 }
601 }
603 // Must be called on the UI thread.
604 // If plugin_pid is -1, the browser will be the active process on return,
605 // otherwise that process will be given focus back before this function returns.
606 static void ReleasePluginFullScreen(pid_t plugin_pid) {
607 // Releasing full screen only works if we are the frontmost process; grab
608 // focus, but give it back to the plugin process if requested.
609 ActivateProcess(base::GetCurrentProcId());
611 nsCocoaUtils::HideOSChromeOnScreen(FALSE, [[NSScreen screens] objectAtIndex:0]);
613 if (plugin_pid != -1) {
614 ActivateProcess(plugin_pid);
615 }
616 }
618 void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid) {
619 bool had_windows = !plugin_visible_windows_set_.empty();
620 plugin_visible_windows_set_.erase(window_id);
621 bool browser_needs_activation = had_windows &&
622 plugin_visible_windows_set_.empty();
624 plugin_modal_windows_set_.erase(window_id);
625 if (plugin_fullscreen_windows_set_.find(window_id) !=
626 plugin_fullscreen_windows_set_.end()) {
627 plugin_fullscreen_windows_set_.erase(window_id);
628 pid_t plugin_pid = browser_needs_activation ? -1 : aPluginPid;
629 browser_needs_activation = false;
630 ReleasePluginFullScreen(plugin_pid);
631 }
633 if (browser_needs_activation) {
634 ActivateProcess(getpid());
635 }
636 }
638 void OnSetCursor(const NSCursorInfo& cursorInfo)
639 {
640 NSCursor* aCursor = cursorInfo.GetNSCursor();
641 if (aCursor) {
642 [aCursor set];
643 }
644 }
646 void OnShowCursor(bool show)
647 {
648 if (show) {
649 [NSCursor unhide];
650 } else {
651 [NSCursor hide];
652 }
653 }
655 void OnPushCursor(const NSCursorInfo& cursorInfo)
656 {
657 NSCursor* aCursor = cursorInfo.GetNSCursor();
658 if (aCursor) {
659 [aCursor push];
660 }
661 }
663 void OnPopCursor()
664 {
665 [NSCursor pop];
666 }
668 } // parent
669 } // namespace mac_plugin_interposing
671 namespace mac_plugin_interposing {
672 namespace child {
674 // TODO(stuartmorgan): Make this an IPC to order the plugin process above the
675 // browser process only if the browser is current frontmost.
676 void FocusPluginProcess() {
677 ProcessSerialNumber this_process, front_process;
678 if ((GetCurrentProcess(&this_process) != noErr) ||
679 (GetFrontProcess(&front_process) != noErr)) {
680 return;
681 }
683 Boolean matched = false;
684 if ((SameProcess(&this_process, &front_process, &matched) == noErr) &&
685 !matched) {
686 SetFrontProcess(&this_process);
687 }
688 }
690 void NotifyBrowserOfPluginShowWindow(uint32_t window_id, CGRect bounds,
691 bool modal) {
692 AssertPluginThread();
694 PluginModuleChild *pmc = PluginModuleChild::current();
695 if (pmc)
696 pmc->PluginShowWindow(window_id, modal, bounds);
697 }
699 void NotifyBrowserOfPluginHideWindow(uint32_t window_id, CGRect bounds) {
700 AssertPluginThread();
702 PluginModuleChild *pmc = PluginModuleChild::current();
703 if (pmc)
704 pmc->PluginHideWindow(window_id);
705 }
707 void NotifyBrowserOfSetCursor(NSCursorInfo& aCursorInfo)
708 {
709 AssertPluginThread();
710 PluginModuleChild *pmc = PluginModuleChild::current();
711 if (pmc) {
712 pmc->SetCursor(aCursorInfo);
713 }
714 }
716 void NotifyBrowserOfShowCursor(bool show)
717 {
718 AssertPluginThread();
719 PluginModuleChild *pmc = PluginModuleChild::current();
720 if (pmc) {
721 pmc->ShowCursor(show);
722 }
723 }
725 void NotifyBrowserOfPushCursor(NSCursorInfo& aCursorInfo)
726 {
727 AssertPluginThread();
728 PluginModuleChild *pmc = PluginModuleChild::current();
729 if (pmc) {
730 pmc->PushCursor(aCursorInfo);
731 }
732 }
734 void NotifyBrowserOfPopCursor()
735 {
736 AssertPluginThread();
737 PluginModuleChild *pmc = PluginModuleChild::current();
738 if (pmc) {
739 pmc->PopCursor();
740 }
741 }
743 struct WindowInfo {
744 uint32_t window_id;
745 CGRect bounds;
746 WindowInfo(NSWindow* window) {
747 NSInteger window_num = [window windowNumber];
748 window_id = window_num > 0 ? window_num : 0;
749 bounds = NSRectToCGRect([window frame]);
750 }
751 };
753 static void OnPluginWindowClosed(const WindowInfo& window_info) {
754 if (window_info.window_id == 0)
755 return;
756 mac_plugin_interposing::child::NotifyBrowserOfPluginHideWindow(window_info.window_id,
757 window_info.bounds);
758 }
760 static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) {
761 // The window id is 0 if it has never been shown (including while it is the
762 // process of being shown for the first time); when that happens, we'll catch
763 // it in _setWindowNumber instead.
764 static BOOL s_pending_display_is_modal = NO;
765 if (window_info.window_id == 0) {
766 if (is_modal)
767 s_pending_display_is_modal = YES;
768 return;
769 }
770 if (s_pending_display_is_modal) {
771 is_modal = YES;
772 s_pending_display_is_modal = NO;
773 }
774 mac_plugin_interposing::child::NotifyBrowserOfPluginShowWindow(
775 window_info.window_id, window_info.bounds, is_modal);
776 }
778 static BOOL OnSetCursor(NSCursorInfo &aInfo)
779 {
780 if (NSCursorInfo::GetNativeCursorsSupported()) {
781 NotifyBrowserOfSetCursor(aInfo);
782 return YES;
783 }
784 return NO;
785 }
787 static BOOL OnHideCursor()
788 {
789 if (NSCursorInfo::GetNativeCursorsSupported()) {
790 NotifyBrowserOfShowCursor(NO);
791 return YES;
792 }
793 return NO;
794 }
796 static BOOL OnUnhideCursor()
797 {
798 if (NSCursorInfo::GetNativeCursorsSupported()) {
799 NotifyBrowserOfShowCursor(YES);
800 return YES;
801 }
802 return NO;
803 }
805 static BOOL OnPushCursor(NSCursorInfo &aInfo)
806 {
807 if (NSCursorInfo::GetNativeCursorsSupported()) {
808 NotifyBrowserOfPushCursor(aInfo);
809 return YES;
810 }
811 return NO;
812 }
814 static BOOL OnPopCursor()
815 {
816 if (NSCursorInfo::GetNativeCursorsSupported()) {
817 NotifyBrowserOfPopCursor();
818 return YES;
819 }
820 return NO;
821 }
823 } // child
824 } // namespace mac_plugin_interposing
826 using namespace mac_plugin_interposing::child;
828 @interface NSWindow (PluginInterposing)
829 - (void)pluginInterpose_orderOut:(id)sender;
830 - (void)pluginInterpose_orderFront:(id)sender;
831 - (void)pluginInterpose_makeKeyAndOrderFront:(id)sender;
832 - (void)pluginInterpose_setWindowNumber:(NSInteger)num;
833 @end
835 @implementation NSWindow (PluginInterposing)
837 - (void)pluginInterpose_orderOut:(id)sender {
838 WindowInfo window_info(self);
839 [self pluginInterpose_orderOut:sender];
840 OnPluginWindowClosed(window_info);
841 }
843 - (void)pluginInterpose_orderFront:(id)sender {
844 mac_plugin_interposing::child::FocusPluginProcess();
845 [self pluginInterpose_orderFront:sender];
846 OnPluginWindowShown(WindowInfo(self), NO);
847 }
849 - (void)pluginInterpose_makeKeyAndOrderFront:(id)sender {
850 mac_plugin_interposing::child::FocusPluginProcess();
851 [self pluginInterpose_makeKeyAndOrderFront:sender];
852 OnPluginWindowShown(WindowInfo(self), NO);
853 }
855 - (void)pluginInterpose_setWindowNumber:(NSInteger)num {
856 if (num > 0)
857 mac_plugin_interposing::child::FocusPluginProcess();
858 [self pluginInterpose_setWindowNumber:num];
859 if (num > 0)
860 OnPluginWindowShown(WindowInfo(self), NO);
861 }
863 @end
865 @interface NSApplication (PluginInterposing)
866 - (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window;
867 @end
869 @implementation NSApplication (PluginInterposing)
871 - (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window {
872 mac_plugin_interposing::child::FocusPluginProcess();
873 // This is out-of-order relative to the other calls, but runModalForWindow:
874 // won't return until the window closes, and the order only matters for
875 // full-screen windows.
876 OnPluginWindowShown(WindowInfo(window), YES);
877 return [self pluginInterpose_runModalForWindow:window];
878 }
880 @end
882 // Hook commands to manipulate the current cursor, so that they can be passed
883 // from the child process to the parent process. These commands have no
884 // effect unless they're performed in the parent process.
885 @interface NSCursor (PluginInterposing)
886 - (void)pluginInterpose_set;
887 - (void)pluginInterpose_push;
888 - (void)pluginInterpose_pop;
889 + (NSCursor*)pluginInterpose_currentCursor;
890 + (void)pluginInterpose_hide;
891 + (void)pluginInterpose_unhide;
892 + (void)pluginInterpose_pop;
893 @end
895 // Cache the results of [NSCursor set], [NSCursor push] and [NSCursor pop].
896 // The last element is always the current cursor.
897 static NSMutableArray* gCursorStack = nil;
899 static BOOL initCursorStack()
900 {
901 if (!gCursorStack) {
902 gCursorStack = [[NSMutableArray arrayWithCapacity:5] retain];
903 }
904 return (gCursorStack != NULL);
905 }
907 static NSCursor* currentCursorFromCache()
908 {
909 if (!initCursorStack())
910 return nil;
911 return (NSCursor*) [gCursorStack lastObject];
912 }
914 static void setCursorInCache(NSCursor* aCursor)
915 {
916 if (!initCursorStack() || !aCursor)
917 return;
918 NSUInteger count = [gCursorStack count];
919 if (count) {
920 [gCursorStack replaceObjectAtIndex:count - 1 withObject:aCursor];
921 } else {
922 [gCursorStack addObject:aCursor];
923 }
924 }
926 static void pushCursorInCache(NSCursor* aCursor)
927 {
928 if (!initCursorStack() || !aCursor)
929 return;
930 [gCursorStack addObject:aCursor];
931 }
933 static void popCursorInCache()
934 {
935 if (!initCursorStack())
936 return;
937 // Apple's doc on the +[NSCursor pop] method says: "If the current cursor
938 // is the only cursor on the stack, this method does nothing."
939 if ([gCursorStack count] > 1) {
940 [gCursorStack removeLastObject];
941 }
942 }
944 @implementation NSCursor (PluginInterposing)
946 - (void)pluginInterpose_set
947 {
948 NSCursorInfo info(self);
949 OnSetCursor(info);
950 setCursorInCache(self);
951 [self pluginInterpose_set];
952 }
954 - (void)pluginInterpose_push
955 {
956 NSCursorInfo info(self);
957 OnPushCursor(info);
958 pushCursorInCache(self);
959 [self pluginInterpose_push];
960 }
962 - (void)pluginInterpose_pop
963 {
964 OnPopCursor();
965 popCursorInCache();
966 [self pluginInterpose_pop];
967 }
969 // The currentCursor method always returns nil when running in a background
970 // process. But this may confuse plugins (notably Flash, see bug 621117). So
971 // if we get a nil return from the "call to super", we return a cursor that's
972 // been cached by previous calls to set or push. According to Apple's docs,
973 // currentCursor "only returns the cursor set by your application using
974 // NSCursor methods". So we don't need to worry about changes to the cursor
975 // made by other methods like SetThemeCursor().
976 + (NSCursor*)pluginInterpose_currentCursor
977 {
978 NSCursor* retval = [self pluginInterpose_currentCursor];
979 if (!retval) {
980 retval = currentCursorFromCache();
981 }
982 return retval;
983 }
985 + (void)pluginInterpose_hide
986 {
987 OnHideCursor();
988 [self pluginInterpose_hide];
989 }
991 + (void)pluginInterpose_unhide
992 {
993 OnUnhideCursor();
994 [self pluginInterpose_unhide];
995 }
997 + (void)pluginInterpose_pop
998 {
999 OnPopCursor();
1000 popCursorInCache();
1001 [self pluginInterpose_pop];
1002 }
1004 @end
1006 static void ExchangeMethods(Class target_class,
1007 BOOL class_method,
1008 SEL original,
1009 SEL replacement) {
1010 Method m1;
1011 Method m2;
1012 if (class_method) {
1013 m1 = class_getClassMethod(target_class, original);
1014 m2 = class_getClassMethod(target_class, replacement);
1015 } else {
1016 m1 = class_getInstanceMethod(target_class, original);
1017 m2 = class_getInstanceMethod(target_class, replacement);
1018 }
1020 if (m1 == m2)
1021 return;
1023 if (m1 && m2)
1024 method_exchangeImplementations(m1, m2);
1025 else
1026 NS_NOTREACHED("Cocoa swizzling failed");
1027 }
1029 namespace mac_plugin_interposing {
1030 namespace child {
1032 void SetUpCocoaInterposing() {
1033 Class nswindow_class = [NSWindow class];
1034 ExchangeMethods(nswindow_class, NO, @selector(orderOut:),
1035 @selector(pluginInterpose_orderOut:));
1036 ExchangeMethods(nswindow_class, NO, @selector(orderFront:),
1037 @selector(pluginInterpose_orderFront:));
1038 ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:),
1039 @selector(pluginInterpose_makeKeyAndOrderFront:));
1040 ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:),
1041 @selector(pluginInterpose_setWindowNumber:));
1043 ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:),
1044 @selector(pluginInterpose_runModalForWindow:));
1046 Class nscursor_class = [NSCursor class];
1047 ExchangeMethods(nscursor_class, NO, @selector(set),
1048 @selector(pluginInterpose_set));
1049 ExchangeMethods(nscursor_class, NO, @selector(push),
1050 @selector(pluginInterpose_push));
1051 ExchangeMethods(nscursor_class, NO, @selector(pop),
1052 @selector(pluginInterpose_pop));
1053 ExchangeMethods(nscursor_class, YES, @selector(currentCursor),
1054 @selector(pluginInterpose_currentCursor));
1055 ExchangeMethods(nscursor_class, YES, @selector(hide),
1056 @selector(pluginInterpose_hide));
1057 ExchangeMethods(nscursor_class, YES, @selector(unhide),
1058 @selector(pluginInterpose_unhide));
1059 ExchangeMethods(nscursor_class, YES, @selector(pop),
1060 @selector(pluginInterpose_pop));
1061 }
1063 } // namespace child
1064 } // namespace mac_plugin_interposing
1066 // Called from plugin_child_interpose.mm, which hooks calls to
1067 // SetCursor() (the QuickDraw call) from the plugin child process.
1068 extern "C" NS_VISIBILITY_DEFAULT BOOL
1069 mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor)
1070 {
1071 NSCursorInfo info(cursor);
1072 return OnSetCursor(info);
1073 }
1075 // Called from plugin_child_interpose.mm, which hooks calls to
1076 // SetThemeCursor() (the Appearance Manager call) from the plugin child
1077 // process.
1078 extern "C" NS_VISIBILITY_DEFAULT BOOL
1079 mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor)
1080 {
1081 NSCursorInfo info;
1082 switch (cursor) {
1083 case kThemeArrowCursor:
1084 info.SetType(NSCursorInfo::TypeArrow);
1085 break;
1086 case kThemeCopyArrowCursor:
1087 info.SetType(NSCursorInfo::TypeDragCopy);
1088 break;
1089 case kThemeAliasArrowCursor:
1090 info.SetType(NSCursorInfo::TypeDragLink);
1091 break;
1092 case kThemeContextualMenuArrowCursor:
1093 info.SetType(NSCursorInfo::TypeContextualMenu);
1094 break;
1095 case kThemeIBeamCursor:
1096 info.SetType(NSCursorInfo::TypeIBeam);
1097 break;
1098 case kThemeCrossCursor:
1099 case kThemePlusCursor:
1100 info.SetType(NSCursorInfo::TypeCrosshair);
1101 break;
1102 case kThemeWatchCursor:
1103 case kThemeSpinningCursor:
1104 info.SetType(NSCursorInfo::TypeArrow);
1105 break;
1106 case kThemeClosedHandCursor:
1107 info.SetType(NSCursorInfo::TypeClosedHand);
1108 break;
1109 case kThemeOpenHandCursor:
1110 info.SetType(NSCursorInfo::TypeOpenHand);
1111 break;
1112 case kThemePointingHandCursor:
1113 case kThemeCountingUpHandCursor:
1114 case kThemeCountingDownHandCursor:
1115 case kThemeCountingUpAndDownHandCursor:
1116 info.SetType(NSCursorInfo::TypePointingHand);
1117 break;
1118 case kThemeResizeLeftCursor:
1119 info.SetType(NSCursorInfo::TypeResizeLeft);
1120 break;
1121 case kThemeResizeRightCursor:
1122 info.SetType(NSCursorInfo::TypeResizeRight);
1123 break;
1124 case kThemeResizeLeftRightCursor:
1125 info.SetType(NSCursorInfo::TypeResizeLeftRight);
1126 break;
1127 case kThemeNotAllowedCursor:
1128 info.SetType(NSCursorInfo::TypeNotAllowed);
1129 break;
1130 case kThemeResizeUpCursor:
1131 info.SetType(NSCursorInfo::TypeResizeUp);
1132 break;
1133 case kThemeResizeDownCursor:
1134 info.SetType(NSCursorInfo::TypeResizeDown);
1135 break;
1136 case kThemeResizeUpDownCursor:
1137 info.SetType(NSCursorInfo::TypeResizeUpDown);
1138 break;
1139 case kThemePoofCursor:
1140 info.SetType(NSCursorInfo::TypeDisappearingItem);
1141 break;
1142 default:
1143 info.SetType(NSCursorInfo::TypeArrow);
1144 break;
1145 }
1146 return OnSetCursor(info);
1147 }
1149 extern "C" NS_VISIBILITY_DEFAULT BOOL
1150 mac_plugin_interposing_child_OnHideCursor()
1151 {
1152 return OnHideCursor();
1153 }
1155 extern "C" NS_VISIBILITY_DEFAULT BOOL
1156 mac_plugin_interposing_child_OnShowCursor()
1157 {
1158 return OnUnhideCursor();
1159 }