Home | History | Annotate | Download | only in plugin
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/plugin/plugin_interpose_util_mac.h"
      6 
      7 #import <AppKit/AppKit.h>
      8 #import <objc/runtime.h>
      9 
     10 #include "content/child/npapi/webplugin_delegate_impl.h"
     11 #include "content/common/plugin_process_messages.h"
     12 #include "content/plugin/plugin_thread.h"
     13 
     14 using content::PluginThread;
     15 
     16 namespace {
     17 
     18 // Brings the plugin process to the front so that the user can see its windows.
     19 void SwitchToPluginProcess() {
     20   ProcessSerialNumber this_process, front_process;
     21   if ((GetCurrentProcess(&this_process) != noErr) ||
     22       (GetFrontProcess(&front_process) != noErr)) {
     23     return;
     24   }
     25 
     26   Boolean matched = false;
     27   if ((SameProcess(&this_process, &front_process, &matched) == noErr) &&
     28       !matched) {
     29     SetFrontProcess(&this_process);
     30   }
     31 }
     32 
     33 // Sends a message to the browser process to inform it that the given window
     34 // has been shown.
     35 void NotifyBrowserOfPluginShowWindow(uint32 window_id, CGRect bounds,
     36                                      bool modal) {
     37   PluginThread* plugin_thread = PluginThread::current();
     38   if (plugin_thread) {
     39     gfx::Rect window_bounds(bounds);
     40     plugin_thread->Send(
     41         new PluginProcessHostMsg_PluginShowWindow(window_id, window_bounds,
     42                                                   modal));
     43   }
     44 }
     45 
     46 // Sends a message to the browser process to inform it that the given window
     47 // has been hidden, and switches focus back to the browser process if there are
     48 // no remaining plugin windows.
     49 void NotifyBrowserOfPluginHideWindow(uint32 window_id, CGRect bounds) {
     50   PluginThread* plugin_thread = PluginThread::current();
     51   if (plugin_thread) {
     52     gfx::Rect window_bounds(bounds);
     53     plugin_thread->Send(
     54         new PluginProcessHostMsg_PluginHideWindow(window_id, window_bounds));
     55   }
     56 }
     57 
     58 // Informs the host that the plugin has changed the cursor visibility.
     59 void NotifyPluginOfSetCursorVisibility(bool visibility) {
     60   PluginThread* plugin_thread = PluginThread::current();
     61   if (plugin_thread) {
     62     plugin_thread->Send(
     63         new PluginProcessHostMsg_PluginSetCursorVisibility(visibility));
     64   }
     65 }
     66 
     67 struct WindowInfo {
     68   uint32 window_id;
     69   CGRect bounds;
     70   WindowInfo(NSWindow* window) {
     71     NSInteger window_num = [window windowNumber];
     72     window_id = window_num > 0 ? window_num : 0;
     73     bounds = NSRectToCGRect([window frame]);
     74   }
     75 };
     76 
     77 void OnPluginWindowClosed(const WindowInfo& window_info) {
     78   if (window_info.window_id == 0)
     79     return;
     80   NotifyBrowserOfPluginHideWindow(window_info.window_id, window_info.bounds);
     81 }
     82 
     83 BOOL g_waiting_for_window_number = NO;
     84 
     85 void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) {
     86   // The window id is 0 if it has never been shown (including while it is the
     87   // process of being shown for the first time); when that happens, we'll catch
     88   // it in _setWindowNumber instead.
     89   static BOOL s_pending_display_is_modal = NO;
     90   if (window_info.window_id == 0) {
     91     g_waiting_for_window_number = YES;
     92     if (is_modal)
     93       s_pending_display_is_modal = YES;
     94     return;
     95   }
     96   g_waiting_for_window_number = NO;
     97   if (s_pending_display_is_modal) {
     98     is_modal = YES;
     99     s_pending_display_is_modal = NO;
    100   }
    101   NotifyBrowserOfPluginShowWindow(window_info.window_id, window_info.bounds,
    102                                   is_modal);
    103 }
    104 
    105 }  // namespace
    106 
    107 @interface NSWindow (ChromePluginUtilities)
    108 // Returns YES if the window is visible and actually on the screen.
    109 - (BOOL)chromePlugin_isWindowOnScreen;
    110 @end
    111 
    112 @implementation NSWindow (ChromePluginUtilities)
    113 
    114 - (BOOL)chromePlugin_isWindowOnScreen {
    115   if (![self isVisible])
    116     return NO;
    117   NSRect window_frame = [self frame];
    118   for (NSScreen* screen in [NSScreen screens]) {
    119     if (NSIntersectsRect(window_frame, [screen frame]))
    120       return YES;
    121   }
    122   return NO;
    123 }
    124 
    125 @end
    126 
    127 @interface NSWindow (ChromePluginInterposing)
    128 - (void)chromePlugin_orderOut:(id)sender;
    129 - (void)chromePlugin_orderFront:(id)sender;
    130 - (void)chromePlugin_makeKeyAndOrderFront:(id)sender;
    131 - (void)chromePlugin_setWindowNumber:(NSInteger)num;
    132 @end
    133 
    134 @implementation NSWindow (ChromePluginInterposing)
    135 
    136 - (void)chromePlugin_orderOut:(id)sender {
    137   WindowInfo window_info(self);
    138   [self chromePlugin_orderOut:sender];
    139   OnPluginWindowClosed(window_info);
    140 }
    141 
    142 - (void)chromePlugin_orderFront:(id)sender {
    143   [self chromePlugin_orderFront:sender];
    144   if ([self chromePlugin_isWindowOnScreen])
    145     SwitchToPluginProcess();
    146   OnPluginWindowShown(WindowInfo(self), NO);
    147 }
    148 
    149 - (void)chromePlugin_makeKeyAndOrderFront:(id)sender {
    150   [self chromePlugin_makeKeyAndOrderFront:sender];
    151   if ([self chromePlugin_isWindowOnScreen])
    152     SwitchToPluginProcess();
    153   OnPluginWindowShown(WindowInfo(self), NO);
    154 }
    155 
    156 - (void)chromePlugin_setWindowNumber:(NSInteger)num {
    157   if (!g_waiting_for_window_number || num <= 0) {
    158     [self chromePlugin_setWindowNumber:num];
    159     return;
    160   }
    161   SwitchToPluginProcess();
    162   [self chromePlugin_setWindowNumber:num];
    163   OnPluginWindowShown(WindowInfo(self), NO);
    164 }
    165 
    166 @end
    167 
    168 @interface NSApplication (ChromePluginInterposing)
    169 - (NSInteger)chromePlugin_runModalForWindow:(NSWindow*)window;
    170 @end
    171 
    172 @implementation NSApplication (ChromePluginInterposing)
    173 
    174 - (NSInteger)chromePlugin_runModalForWindow:(NSWindow*)window {
    175   SwitchToPluginProcess();
    176   // This is out-of-order relative to the other calls, but runModalForWindow:
    177   // won't return until the window closes, and the order only matters for
    178   // full-screen windows.
    179   OnPluginWindowShown(WindowInfo(window), YES);
    180   return [self chromePlugin_runModalForWindow:window];
    181 }
    182 
    183 @end
    184 
    185 @interface NSCursor (ChromePluginInterposing)
    186 - (void)chromePlugin_set;
    187 + (void)chromePlugin_hide;
    188 + (void)chromePlugin_unhide;
    189 @end
    190 
    191 @implementation NSCursor (ChromePluginInterposing)
    192 
    193 - (void)chromePlugin_set {
    194   content::WebPluginDelegateImpl* delegate =
    195       content::WebPluginDelegateImpl::GetActiveDelegate();
    196   if (delegate) {
    197     delegate->SetNSCursor(self);
    198     return;
    199   }
    200   [self chromePlugin_set];
    201 }
    202 
    203 + (void)chromePlugin_hide {
    204   NotifyPluginOfSetCursorVisibility(false);
    205 }
    206 
    207 + (void)chromePlugin_unhide {
    208   NotifyPluginOfSetCursorVisibility(true);
    209 }
    210 
    211 @end
    212 
    213 #pragma mark -
    214 
    215 static void ExchangeMethods(Class target_class,
    216                             BOOL class_method,
    217                             SEL original,
    218                             SEL replacement) {
    219   Method m1;
    220   Method m2;
    221   if (class_method) {
    222     m1 = class_getClassMethod(target_class, original);
    223     m2 = class_getClassMethod(target_class, replacement);
    224   } else {
    225     m1 = class_getInstanceMethod(target_class, original);
    226     m2 = class_getInstanceMethod(target_class, replacement);
    227   }
    228   if (m1 && m2)
    229     method_exchangeImplementations(m1, m2);
    230   else
    231     NOTREACHED() << "Cocoa swizzling failed";
    232 }
    233 
    234 namespace mac_plugin_interposing {
    235 
    236 void SetUpCocoaInterposing() {
    237   Class nswindow_class = [NSWindow class];
    238   ExchangeMethods(nswindow_class, NO, @selector(orderOut:),
    239                   @selector(chromePlugin_orderOut:));
    240   ExchangeMethods(nswindow_class, NO, @selector(orderFront:),
    241                   @selector(chromePlugin_orderFront:));
    242   ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:),
    243                   @selector(chromePlugin_makeKeyAndOrderFront:));
    244   ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:),
    245                   @selector(chromePlugin_setWindowNumber:));
    246 
    247   ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:),
    248                   @selector(chromePlugin_runModalForWindow:));
    249 
    250   Class nscursor_class = [NSCursor class];
    251   ExchangeMethods(nscursor_class, NO, @selector(set),
    252                   @selector(chromePlugin_set));
    253   ExchangeMethods(nscursor_class, YES, @selector(hide),
    254                   @selector(chromePlugin_hide));
    255   ExchangeMethods(nscursor_class, YES, @selector(unhide),
    256                   @selector(chromePlugin_unhide));
    257 }
    258 
    259 }  // namespace mac_plugin_interposing
    260