| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/plugin/plugin_interpose_util_mac.h" |
| |
| #import <AppKit/AppKit.h> |
| #import <objc/runtime.h> |
| |
| #include "content/common/plugin_messages.h" |
| #include "content/plugin/plugin_thread.h" |
| #include "webkit/plugins/npapi/webplugin_delegate_impl.h" |
| |
| namespace mac_plugin_interposing { |
| |
| // TODO(stuartmorgan): Make this an IPC to order the plugin process above the |
| // browser process only if the browser is current frontmost. |
| __attribute__((visibility("default"))) |
| void SwitchToPluginProcess() { |
| ProcessSerialNumber this_process, front_process; |
| if ((GetCurrentProcess(&this_process) != noErr) || |
| (GetFrontProcess(&front_process) != noErr)) { |
| return; |
| } |
| |
| Boolean matched = false; |
| if ((SameProcess(&this_process, &front_process, &matched) == noErr) && |
| !matched) { |
| SetFrontProcess(&this_process); |
| } |
| } |
| |
| __attribute__((visibility("default"))) |
| OpaquePluginRef GetActiveDelegate() { |
| return webkit::npapi::WebPluginDelegateImpl::GetActiveDelegate(); |
| } |
| |
| __attribute__((visibility("default"))) |
| void NotifyBrowserOfPluginSelectWindow(uint32 window_id, CGRect bounds, |
| bool modal) { |
| PluginThread* plugin_thread = PluginThread::current(); |
| if (plugin_thread) { |
| gfx::Rect window_bounds(bounds); |
| plugin_thread->Send( |
| new PluginProcessHostMsg_PluginSelectWindow(window_id, window_bounds, |
| modal)); |
| } |
| } |
| |
| __attribute__((visibility("default"))) |
| void NotifyBrowserOfPluginShowWindow(uint32 window_id, CGRect bounds, |
| bool modal) { |
| PluginThread* plugin_thread = PluginThread::current(); |
| if (plugin_thread) { |
| gfx::Rect window_bounds(bounds); |
| plugin_thread->Send( |
| new PluginProcessHostMsg_PluginShowWindow(window_id, window_bounds, |
| modal)); |
| } |
| } |
| |
| __attribute__((visibility("default"))) |
| void NotifyBrowserOfPluginHideWindow(uint32 window_id, CGRect bounds) { |
| PluginThread* plugin_thread = PluginThread::current(); |
| if (plugin_thread) { |
| gfx::Rect window_bounds(bounds); |
| plugin_thread->Send( |
| new PluginProcessHostMsg_PluginHideWindow(window_id, window_bounds)); |
| } |
| } |
| |
| __attribute__((visibility("default"))) |
| void NotifyPluginOfSetThemeCursor(OpaquePluginRef delegate, |
| ThemeCursor cursor) { |
| static_cast<webkit::npapi::WebPluginDelegateImpl*>(delegate)->SetThemeCursor( |
| cursor); |
| } |
| |
| __attribute__((visibility("default"))) |
| void NotifyPluginOfSetCursor(OpaquePluginRef delegate, |
| const Cursor* cursor) { |
| static_cast<webkit::npapi::WebPluginDelegateImpl*>(delegate)->SetCarbonCursor( |
| cursor); |
| } |
| |
| void NotifyPluginOfSetCursorVisibility(bool visibility) { |
| PluginThread* plugin_thread = PluginThread::current(); |
| if (plugin_thread) { |
| plugin_thread->Send( |
| new PluginProcessHostMsg_PluginSetCursorVisibility(visibility)); |
| } |
| } |
| |
| __attribute__((visibility("default"))) |
| bool GetPluginWindowHasFocus(const OpaquePluginRef delegate) { |
| return static_cast<webkit::npapi::WebPluginDelegateImpl*>( |
| delegate)->GetWindowHasFocus(); |
| } |
| |
| } // namespace mac_plugin_interposing |
| |
| #pragma mark - |
| |
| struct WindowInfo { |
| uint32 window_id; |
| CGRect bounds; |
| WindowInfo(NSWindow* window) { |
| NSInteger window_num = [window windowNumber]; |
| window_id = window_num > 0 ? window_num : 0; |
| bounds = NSRectToCGRect([window frame]); |
| } |
| }; |
| |
| static void OnPluginWindowClosed(const WindowInfo& window_info) { |
| if (window_info.window_id == 0) |
| return; |
| mac_plugin_interposing::NotifyBrowserOfPluginHideWindow(window_info.window_id, |
| window_info.bounds); |
| } |
| |
| static BOOL g_waiting_for_window_number = NO; |
| |
| static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) { |
| // The window id is 0 if it has never been shown (including while it is the |
| // process of being shown for the first time); when that happens, we'll catch |
| // it in _setWindowNumber instead. |
| static BOOL s_pending_display_is_modal = NO; |
| if (window_info.window_id == 0) { |
| g_waiting_for_window_number = YES; |
| if (is_modal) |
| s_pending_display_is_modal = YES; |
| return; |
| } |
| g_waiting_for_window_number = NO; |
| if (s_pending_display_is_modal) { |
| is_modal = YES; |
| s_pending_display_is_modal = NO; |
| } |
| mac_plugin_interposing::NotifyBrowserOfPluginShowWindow( |
| window_info.window_id, window_info.bounds, is_modal); |
| } |
| |
| @interface NSWindow (ChromePluginUtilities) |
| // Returns YES if the window is visible and actually on the screen. |
| - (BOOL)chromePlugin_isWindowOnScreen; |
| // Returns YES if the window is the dummy window we use for popup menus; |
| // see PluginInstance::PopUpContextMenu. |
| // This can be removed once 10.5 is no longer supported. |
| - (BOOL)chromePlugin_isPopupMenuWindow; |
| @end |
| |
| @implementation NSWindow (ChromePluginUtilities) |
| |
| - (BOOL)chromePlugin_isWindowOnScreen { |
| if (![self isVisible]) |
| return NO; |
| NSRect window_frame = [self frame]; |
| for (NSScreen* screen in [NSScreen screens]) { |
| if (NSIntersectsRect(window_frame, [screen frame])) |
| return YES; |
| } |
| return NO; |
| } |
| |
| - (BOOL)chromePlugin_isPopupMenuWindow { |
| return [[self title] isEqualToString:@"PopupMenuDummy"]; |
| } |
| |
| @end |
| |
| @interface NSWindow (ChromePluginInterposing) |
| - (void)chromePlugin_orderOut:(id)sender; |
| - (void)chromePlugin_orderFront:(id)sender; |
| - (void)chromePlugin_makeKeyAndOrderFront:(id)sender; |
| - (void)chromePlugin_setWindowNumber:(NSInteger)num; |
| @end |
| |
| @implementation NSWindow (ChromePluginInterposing) |
| |
| - (void)chromePlugin_orderOut:(id)sender { |
| WindowInfo window_info(self); |
| [self chromePlugin_orderOut:sender]; |
| OnPluginWindowClosed(window_info); |
| } |
| |
| - (void)chromePlugin_orderFront:(id)sender { |
| [self chromePlugin_orderFront:sender]; |
| if ([self chromePlugin_isWindowOnScreen] && |
| ![self chromePlugin_isPopupMenuWindow]) |
| mac_plugin_interposing::SwitchToPluginProcess(); |
| OnPluginWindowShown(WindowInfo(self), NO); |
| } |
| |
| - (void)chromePlugin_makeKeyAndOrderFront:(id)sender { |
| [self chromePlugin_makeKeyAndOrderFront:sender]; |
| if ([self chromePlugin_isWindowOnScreen] && |
| ![self chromePlugin_isPopupMenuWindow]) |
| mac_plugin_interposing::SwitchToPluginProcess(); |
| OnPluginWindowShown(WindowInfo(self), NO); |
| } |
| |
| - (void)chromePlugin_setWindowNumber:(NSInteger)num { |
| if (!g_waiting_for_window_number || num <= 0) { |
| [self chromePlugin_setWindowNumber:num]; |
| return; |
| } |
| if (![self chromePlugin_isPopupMenuWindow]) |
| mac_plugin_interposing::SwitchToPluginProcess(); |
| [self chromePlugin_setWindowNumber:num]; |
| OnPluginWindowShown(WindowInfo(self), NO); |
| } |
| |
| @end |
| |
| @interface NSApplication (ChromePluginInterposing) |
| - (NSInteger)chromePlugin_runModalForWindow:(NSWindow*)window; |
| @end |
| |
| @implementation NSApplication (ChromePluginInterposing) |
| |
| - (NSInteger)chromePlugin_runModalForWindow:(NSWindow*)window { |
| mac_plugin_interposing::SwitchToPluginProcess(); |
| // This is out-of-order relative to the other calls, but runModalForWindow: |
| // won't return until the window closes, and the order only matters for |
| // full-screen windows. |
| OnPluginWindowShown(WindowInfo(window), YES); |
| return [self chromePlugin_runModalForWindow:window]; |
| } |
| |
| @end |
| |
| @interface NSCursor (ChromePluginInterposing) |
| - (void)chromePlugin_set; |
| + (void)chromePlugin_hide; |
| + (void)chromePlugin_unhide; |
| @end |
| |
| @implementation NSCursor (ChromePluginInterposing) |
| |
| - (void)chromePlugin_set { |
| OpaquePluginRef delegate = mac_plugin_interposing::GetActiveDelegate(); |
| if (delegate) { |
| static_cast<webkit::npapi::WebPluginDelegateImpl*>(delegate)->SetNSCursor( |
| self); |
| return; |
| } |
| [self chromePlugin_set]; |
| } |
| |
| + (void)chromePlugin_hide { |
| mac_plugin_interposing::NotifyPluginOfSetCursorVisibility(false); |
| } |
| |
| + (void)chromePlugin_unhide { |
| mac_plugin_interposing::NotifyPluginOfSetCursorVisibility(true); |
| } |
| |
| @end |
| |
| #pragma mark - |
| |
| static void ExchangeMethods(Class target_class, |
| BOOL class_method, |
| SEL original, |
| SEL replacement) { |
| Method m1; |
| Method m2; |
| if (class_method) { |
| m1 = class_getClassMethod(target_class, original); |
| m2 = class_getClassMethod(target_class, replacement); |
| } else { |
| m1 = class_getInstanceMethod(target_class, original); |
| m2 = class_getInstanceMethod(target_class, replacement); |
| } |
| if (m1 && m2) |
| method_exchangeImplementations(m1, m2); |
| else |
| NOTREACHED() << "Cocoa swizzling failed"; |
| } |
| |
| namespace mac_plugin_interposing { |
| |
| void SetUpCocoaInterposing() { |
| Class nswindow_class = [NSWindow class]; |
| ExchangeMethods(nswindow_class, NO, @selector(orderOut:), |
| @selector(chromePlugin_orderOut:)); |
| ExchangeMethods(nswindow_class, NO, @selector(orderFront:), |
| @selector(chromePlugin_orderFront:)); |
| ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:), |
| @selector(chromePlugin_makeKeyAndOrderFront:)); |
| ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:), |
| @selector(chromePlugin_setWindowNumber:)); |
| |
| ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:), |
| @selector(chromePlugin_runModalForWindow:)); |
| |
| Class nscursor_class = [NSCursor class]; |
| ExchangeMethods(nscursor_class, NO, @selector(set), |
| @selector(chromePlugin_set)); |
| ExchangeMethods(nscursor_class, YES, @selector(hide), |
| @selector(chromePlugin_hide)); |
| ExchangeMethods(nscursor_class, YES, @selector(unhide), |
| @selector(chromePlugin_unhide)); |
| } |
| |
| } // namespace mac_plugin_interposing |