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 { 17 18// Brings the plugin process to the front so that the user can see its windows. 19void 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. 35void 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. 49void 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. 59void 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 67struct 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 77void 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 83BOOL g_waiting_for_window_number = NO; 84 85void 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 215static 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 234namespace mac_plugin_interposing { 235 236void 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