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 14using content::PluginThread; 15 16namespace 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"))) 21void 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"))) 36OpaquePluginRef GetActiveDelegate() { 37 return content::WebPluginDelegateImpl::GetActiveDelegate(); 38} 39 40__attribute__((visibility("default"))) 41void 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"))) 53void 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"))) 65void 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 74void 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 86struct 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 96static 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 103static BOOL g_waiting_for_window_number = NO; 104 105static 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 232static 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 251namespace mac_plugin_interposing { 252 253void 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