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