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