1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/cocoa/nsToolkit.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,325 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsToolkit.h" 1.10 + 1.11 +#include <ctype.h> 1.12 +#include <stdlib.h> 1.13 +#include <stdio.h> 1.14 + 1.15 +#include <mach/mach_port.h> 1.16 +#include <mach/mach_interface.h> 1.17 +#include <mach/mach_init.h> 1.18 + 1.19 +extern "C" { 1.20 +#include <mach-o/getsect.h> 1.21 +} 1.22 +#include <unistd.h> 1.23 +#include <dlfcn.h> 1.24 + 1.25 +#import <Cocoa/Cocoa.h> 1.26 +#import <IOKit/pwr_mgt/IOPMLib.h> 1.27 +#import <IOKit/IOMessage.h> 1.28 + 1.29 +#include "nsCocoaUtils.h" 1.30 +#include "nsObjCExceptions.h" 1.31 + 1.32 +#include "nsGkAtoms.h" 1.33 +#include "nsIRollupListener.h" 1.34 +#include "nsIWidget.h" 1.35 +#include "nsBaseWidget.h" 1.36 + 1.37 +#include "nsIObserverService.h" 1.38 +#include "nsIServiceManager.h" 1.39 + 1.40 +#include "mozilla/Preferences.h" 1.41 + 1.42 +using namespace mozilla; 1.43 + 1.44 +static io_connect_t gRootPort = MACH_PORT_NULL; 1.45 + 1.46 +nsToolkit* nsToolkit::gToolkit = nullptr; 1.47 + 1.48 +nsToolkit::nsToolkit() 1.49 +: mSleepWakeNotificationRLS(nullptr) 1.50 +, mEventTapPort(nullptr) 1.51 +, mEventTapRLS(nullptr) 1.52 +{ 1.53 + MOZ_COUNT_CTOR(nsToolkit); 1.54 + RegisterForSleepWakeNotifcations(); 1.55 +} 1.56 + 1.57 +nsToolkit::~nsToolkit() 1.58 +{ 1.59 + MOZ_COUNT_DTOR(nsToolkit); 1.60 + RemoveSleepWakeNotifcations(); 1.61 + UnregisterAllProcessMouseEventHandlers(); 1.62 +} 1.63 + 1.64 +void 1.65 +nsToolkit::PostSleepWakeNotification(const char* aNotification) 1.66 +{ 1.67 + nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1"); 1.68 + if (observerService) 1.69 + observerService->NotifyObservers(nullptr, aNotification, nullptr); 1.70 +} 1.71 + 1.72 +// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html 1.73 +static void ToolkitSleepWakeCallback(void *refCon, io_service_t service, natural_t messageType, void * messageArgument) 1.74 +{ 1.75 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.76 + 1.77 + switch (messageType) 1.78 + { 1.79 + case kIOMessageSystemWillSleep: 1.80 + // System is going to sleep now. 1.81 + nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC); 1.82 + ::IOAllowPowerChange(gRootPort, (long)messageArgument); 1.83 + break; 1.84 + 1.85 + case kIOMessageCanSystemSleep: 1.86 + // In this case, the computer has been idle for several minutes 1.87 + // and will sleep soon so you must either allow or cancel 1.88 + // this notification. Important: if you don’t respond, there will 1.89 + // be a 30-second timeout before the computer sleeps. 1.90 + // In Mozilla's case, we always allow sleep. 1.91 + ::IOAllowPowerChange(gRootPort,(long)messageArgument); 1.92 + break; 1.93 + 1.94 + case kIOMessageSystemHasPoweredOn: 1.95 + // Handle wakeup. 1.96 + nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC); 1.97 + break; 1.98 + } 1.99 + 1.100 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.101 +} 1.102 + 1.103 +nsresult 1.104 +nsToolkit::RegisterForSleepWakeNotifcations() 1.105 +{ 1.106 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.107 + 1.108 + IONotificationPortRef notifyPortRef; 1.109 + 1.110 + NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake"); 1.111 + 1.112 + gRootPort = ::IORegisterForSystemPower(0, ¬ifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier); 1.113 + if (gRootPort == MACH_PORT_NULL) { 1.114 + NS_ERROR("IORegisterForSystemPower failed"); 1.115 + return NS_ERROR_FAILURE; 1.116 + } 1.117 + 1.118 + mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef); 1.119 + ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), 1.120 + mSleepWakeNotificationRLS, 1.121 + kCFRunLoopDefaultMode); 1.122 + 1.123 + return NS_OK; 1.124 + 1.125 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.126 +} 1.127 + 1.128 +void 1.129 +nsToolkit::RemoveSleepWakeNotifcations() 1.130 +{ 1.131 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.132 + 1.133 + if (mSleepWakeNotificationRLS) { 1.134 + ::IODeregisterForSystemPower(&mPowerNotifier); 1.135 + ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), 1.136 + mSleepWakeNotificationRLS, 1.137 + kCFRunLoopDefaultMode); 1.138 + 1.139 + mSleepWakeNotificationRLS = nullptr; 1.140 + } 1.141 + 1.142 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.143 +} 1.144 + 1.145 +// Converts aPoint from the CoreGraphics "global display coordinate" system 1.146 +// (which includes all displays/screens and has a top-left origin) to its 1.147 +// (presumed) Cocoa counterpart (assumed to be the same as the "screen 1.148 +// coordinates" system), which has a bottom-left origin. 1.149 +static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint) 1.150 +{ 1.151 + NSPoint cocoaPoint; 1.152 + cocoaPoint.x = aPoint.x; 1.153 + cocoaPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y); 1.154 + return cocoaPoint; 1.155 +} 1.156 + 1.157 +// Since our event tap is "listen only", events arrive here a little after 1.158 +// they've already been processed. 1.159 +static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) 1.160 +{ 1.161 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.162 + 1.163 + if ((type == kCGEventTapDisabledByUserInput) || 1.164 + (type == kCGEventTapDisabledByTimeout)) 1.165 + return event; 1.166 + if ([NSApp isActive]) 1.167 + return event; 1.168 + 1.169 + nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); 1.170 + NS_ENSURE_TRUE(rollupListener, event); 1.171 + nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); 1.172 + if (!rollupWidget) 1.173 + return event; 1.174 + 1.175 + // Don't bother with rightMouseDown events here -- because of the delay, 1.176 + // we'll end up closing browser context menus that we just opened. Since 1.177 + // these events usually raise a context menu, we'll handle them by hooking 1.178 + // the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed 1.179 + // notification (in nsAppShell.mm's AppShellDelegate). 1.180 + if (type == kCGEventRightMouseDown) 1.181 + return event; 1.182 + NSWindow *ctxMenuWindow = (NSWindow*) rollupWidget->GetNativeData(NS_NATIVE_WINDOW); 1.183 + if (!ctxMenuWindow) 1.184 + return event; 1.185 + NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event)); 1.186 + // Don't roll up the rollup widget if our mouseDown happens over it (doing 1.187 + // so would break the corresponding context menu). 1.188 + if (NSPointInRect(screenLocation, [ctxMenuWindow frame])) 1.189 + return event; 1.190 + rollupListener->Rollup(0, nullptr, nullptr); 1.191 + return event; 1.192 + 1.193 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL); 1.194 +} 1.195 + 1.196 +// Cocoa Firefox's use of custom context menus requires that we explicitly 1.197 +// handle mouse events from other processes that the OS handles 1.198 +// "automatically" for native context menus -- mouseMoved events so that 1.199 +// right-click context menus work properly when our browser doesn't have the 1.200 +// focus (bmo bug 368077), and mouseDown events so that our browser can 1.201 +// dismiss a context menu when a mouseDown happens in another process (bmo 1.202 +// bug 339945). 1.203 +void 1.204 +nsToolkit::RegisterForAllProcessMouseEvents() 1.205 +{ 1.206 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.207 + 1.208 + if (getenv("MOZ_DEBUG")) 1.209 + return; 1.210 + 1.211 + // Don't do this for apps that (like Camino) use native context menus. 1.212 +#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS 1.213 + return; 1.214 +#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */ 1.215 + 1.216 + if (!mEventTapRLS) { 1.217 + // Using an event tap for mouseDown events (instead of installing a 1.218 + // handler for them on the EventMonitor target) works around an Apple 1.219 + // bug that causes OS menus (like the Clock menu) not to work properly 1.220 + // on OS X 10.4.X and below (bmo bug 381448). 1.221 + // We install our event tap "listen only" to get around yet another Apple 1.222 + // bug -- when we install it as an event filter on any kind of mouseDown 1.223 + // event, that kind of event stops working in the main menu, and usually 1.224 + // mouse event processing stops working in all apps in the current login 1.225 + // session (so the entire OS appears to be hung)! The downside of 1.226 + // installing listen-only is that events arrive at our handler slightly 1.227 + // after they've already been processed. 1.228 + mEventTapPort = CGEventTapCreate(kCGSessionEventTap, 1.229 + kCGHeadInsertEventTap, 1.230 + kCGEventTapOptionListenOnly, 1.231 + CGEventMaskBit(kCGEventLeftMouseDown) 1.232 + | CGEventMaskBit(kCGEventRightMouseDown) 1.233 + | CGEventMaskBit(kCGEventOtherMouseDown), 1.234 + EventTapCallback, 1.235 + nullptr); 1.236 + if (!mEventTapPort) 1.237 + return; 1.238 + mEventTapRLS = CFMachPortCreateRunLoopSource(nullptr, mEventTapPort, 0); 1.239 + if (!mEventTapRLS) { 1.240 + CFRelease(mEventTapPort); 1.241 + mEventTapPort = nullptr; 1.242 + return; 1.243 + } 1.244 + CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode); 1.245 + } 1.246 + 1.247 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.248 +} 1.249 + 1.250 +void 1.251 +nsToolkit::UnregisterAllProcessMouseEventHandlers() 1.252 +{ 1.253 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.254 + 1.255 + if (mEventTapRLS) { 1.256 + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS, 1.257 + kCFRunLoopDefaultMode); 1.258 + CFRelease(mEventTapRLS); 1.259 + mEventTapRLS = nullptr; 1.260 + } 1.261 + if (mEventTapPort) { 1.262 + // mEventTapPort must be invalidated as well as released. Otherwise the 1.263 + // event tap doesn't get destroyed until the browser process ends (it 1.264 + // keeps showing up in the list returned by CGGetEventTapList()). 1.265 + CFMachPortInvalidate(mEventTapPort); 1.266 + CFRelease(mEventTapPort); 1.267 + mEventTapPort = nullptr; 1.268 + } 1.269 + 1.270 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.271 +} 1.272 + 1.273 +// Return the nsToolkit instance. If a toolkit does not yet exist, then one 1.274 +// will be created. 1.275 +// static 1.276 +nsToolkit* nsToolkit::GetToolkit() 1.277 +{ 1.278 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.279 + 1.280 + if (!gToolkit) { 1.281 + gToolkit = new nsToolkit(); 1.282 + } 1.283 + 1.284 + return gToolkit; 1.285 + 1.286 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr); 1.287 +} 1.288 + 1.289 +// An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X 1.290 +// Leopard and is available to 64-bit binaries on Leopard and above. Based on 1.291 +// ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling. 1.292 +// Since the Method type becomes an opaque type as of Objective-C 2.0, we'll 1.293 +// have to switch to using accessor methods like method_exchangeImplementations() 1.294 +// when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard 1.295 +// and above). 1.296 +// 1.297 +// Be aware that, if aClass doesn't have an orgMethod selector but one of its 1.298 +// superclasses does, the method substitution will (in effect) take place in 1.299 +// that superclass (rather than in aClass itself). The substitution has 1.300 +// effect on the class where it takes place and all of that class's 1.301 +// subclasses. In order for method swizzling to work properly, posedMethod 1.302 +// needs to be unique in the class where the substitution takes place and all 1.303 +// of its subclasses. 1.304 +nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod, 1.305 + bool classMethods) 1.306 +{ 1.307 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.308 + 1.309 + Method original = nil; 1.310 + Method posed = nil; 1.311 + 1.312 + if (classMethods) { 1.313 + original = class_getClassMethod(aClass, orgMethod); 1.314 + posed = class_getClassMethod(aClass, posedMethod); 1.315 + } else { 1.316 + original = class_getInstanceMethod(aClass, orgMethod); 1.317 + posed = class_getInstanceMethod(aClass, posedMethod); 1.318 + } 1.319 + 1.320 + if (!original || !posed) 1.321 + return NS_ERROR_FAILURE; 1.322 + 1.323 + method_exchangeImplementations(original, posed); 1.324 + 1.325 + return NS_OK; 1.326 + 1.327 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.328 +}