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