1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2/* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6#include "nsToolkit.h" 7 8#include <ctype.h> 9#include <stdlib.h> 10#include <stdio.h> 11 12#include <mach/mach_port.h> 13#include <mach/mach_interface.h> 14#include <mach/mach_init.h> 15 16extern "C" { 17#include <mach-o/getsect.h> 18} 19#include <unistd.h> 20#include <dlfcn.h> 21 22#import <Cocoa/Cocoa.h> 23#import <IOKit/pwr_mgt/IOPMLib.h> 24#import <IOKit/IOMessage.h> 25 26#include "nsCocoaUtils.h" 27#include "nsObjCExceptions.h" 28 29#include "nsGkAtoms.h" 30#include "nsIRollupListener.h" 31#include "nsIWidget.h" 32#include "nsBaseWidget.h" 33 34#include "nsIObserverService.h" 35 36#include "mozilla/Preferences.h" 37#include "mozilla/Services.h" 38 39#include "NativeMenuSupport.h" 40 41using namespace mozilla; 42 43static io_connect_t gRootPort = MACH_PORT_NULL; 44 45nsToolkit* nsToolkit::gToolkit = nullptr; 46 47nsToolkit::nsToolkit() 48 : mSleepWakeNotificationRLS(nullptr), mPowerNotifier{0}, mAllProcessMouseMonitor(nil) { 49 MOZ_COUNT_CTOR(nsToolkit); 50 RegisterForSleepWakeNotifications(); 51} 52 53nsToolkit::~nsToolkit() { 54 MOZ_COUNT_DTOR(nsToolkit); 55 RemoveSleepWakeNotifications(); 56 StopMonitoringAllProcessMouseEvents(); 57} 58 59void nsToolkit::PostSleepWakeNotification(const char* aNotification) { 60 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); 61 if (observerService) observerService->NotifyObservers(nullptr, aNotification, nullptr); 62} 63 64// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html 65static void ToolkitSleepWakeCallback(void* refCon, io_service_t service, natural_t messageType, 66 void* messageArgument) { 67 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 68 69 switch (messageType) { 70 case kIOMessageSystemWillSleep: 71 // System is going to sleep now. 72 nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC); 73 ::IOAllowPowerChange(gRootPort, (long)messageArgument); 74 break; 75 76 case kIOMessageCanSystemSleep: 77 // In this case, the computer has been idle for several minutes 78 // and will sleep soon so you must either allow or cancel 79 // this notification. Important: if you don’t respond, there will 80 // be a 30-second timeout before the computer sleeps. 81 // In Mozilla's case, we always allow sleep. 82 ::IOAllowPowerChange(gRootPort, (long)messageArgument); 83 break; 84 85 case kIOMessageSystemHasPoweredOn: 86 // Handle wakeup. 87 nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC); 88 break; 89 } 90 91 NS_OBJC_END_TRY_IGNORE_BLOCK; 92} 93 94nsresult nsToolkit::RegisterForSleepWakeNotifications() { 95 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 96 97 IONotificationPortRef notifyPortRef; 98 99 NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake"); 100 101 gRootPort = 102 ::IORegisterForSystemPower(0, ¬ifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier); 103 if (gRootPort == MACH_PORT_NULL) { 104 NS_ERROR("IORegisterForSystemPower failed"); 105 return NS_ERROR_FAILURE; 106 } 107 108 mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef); 109 ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mSleepWakeNotificationRLS, kCFRunLoopDefaultMode); 110 111 return NS_OK; 112 113 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 114} 115 116void nsToolkit::RemoveSleepWakeNotifications() { 117 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 118 119 if (mSleepWakeNotificationRLS) { 120 ::IODeregisterForSystemPower(&mPowerNotifier); 121 ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mSleepWakeNotificationRLS, 122 kCFRunLoopDefaultMode); 123 124 mSleepWakeNotificationRLS = nullptr; 125 } 126 127 NS_OBJC_END_TRY_IGNORE_BLOCK; 128} 129 130// Cocoa Firefox's use of custom context menus requires that we explicitly 131// handle mouse events from other processes that the OS handles 132// "automatically" for native context menus -- mouseMoved events so that 133// right-click context menus work properly when our browser doesn't have the 134// focus (bmo bug 368077), and mouseDown events so that our browser can 135// dismiss a context menu when a mouseDown happens in another process (bmo 136// bug 339945). 137void nsToolkit::MonitorAllProcessMouseEvents() { 138 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 139 140 if (mozilla::widget::NativeMenuSupport::ShouldUseNativeContextMenus()) { 141 // Don't do this if we are using native context menus. 142 return; 143 } 144 145 if (getenv("MOZ_NO_GLOBAL_MOUSE_MONITOR")) return; 146 147 if (mAllProcessMouseMonitor == nil) { 148 mAllProcessMouseMonitor = [NSEvent 149 addGlobalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown | NSEventMaskLeftMouseDown 150 handler:^(NSEvent* evt) { 151 if ([NSApp isActive]) { 152 return; 153 } 154 155 nsIRollupListener* rollupListener = 156 nsBaseWidget::GetActiveRollupListener(); 157 if (!rollupListener) { 158 return; 159 } 160 161 nsCOMPtr<nsIWidget> rollupWidget = 162 rollupListener->GetRollupWidget(); 163 if (!rollupWidget) { 164 return; 165 } 166 167 NSWindow* ctxMenuWindow = 168 (NSWindow*)rollupWidget->GetNativeData( 169 NS_NATIVE_WINDOW); 170 if (!ctxMenuWindow) { 171 return; 172 } 173 174 // Don't roll up the rollup widget if our mouseDown happens 175 // over it (doing so would break the corresponding context 176 // menu). 177 NSPoint screenLocation = [NSEvent mouseLocation]; 178 if (NSPointInRect(screenLocation, [ctxMenuWindow frame])) { 179 return; 180 } 181 182 rollupListener->Rollup(0, false, nullptr, nullptr); 183 }]; 184 } 185 186 NS_OBJC_END_TRY_IGNORE_BLOCK; 187} 188 189void nsToolkit::StopMonitoringAllProcessMouseEvents() { 190 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 191 192 if (mAllProcessMouseMonitor != nil) { 193 [NSEvent removeMonitor:mAllProcessMouseMonitor]; 194 mAllProcessMouseMonitor = nil; 195 } 196 197 NS_OBJC_END_TRY_IGNORE_BLOCK; 198} 199 200// Return the nsToolkit instance. If a toolkit does not yet exist, then one 201// will be created. 202// static 203nsToolkit* nsToolkit::GetToolkit() { 204 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 205 206 if (!gToolkit) { 207 gToolkit = new nsToolkit(); 208 } 209 210 return gToolkit; 211 212 NS_OBJC_END_TRY_BLOCK_RETURN(nullptr); 213} 214 215// An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X 216// Leopard and is available to 64-bit binaries on Leopard and above. Based on 217// ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling. 218// Since the Method type becomes an opaque type as of Objective-C 2.0, we'll 219// have to switch to using accessor methods like method_exchangeImplementations() 220// when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard 221// and above). 222// 223// Be aware that, if aClass doesn't have an orgMethod selector but one of its 224// superclasses does, the method substitution will (in effect) take place in 225// that superclass (rather than in aClass itself). The substitution has 226// effect on the class where it takes place and all of that class's 227// subclasses. In order for method swizzling to work properly, posedMethod 228// needs to be unique in the class where the substitution takes place and all 229// of its subclasses. 230nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod, 231 bool classMethods) { 232 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 233 234 Method original = nil; 235 Method posed = nil; 236 237 if (classMethods) { 238 original = class_getClassMethod(aClass, orgMethod); 239 posed = class_getClassMethod(aClass, posedMethod); 240 } else { 241 original = class_getInstanceMethod(aClass, orgMethod); 242 posed = class_getInstanceMethod(aClass, posedMethod); 243 } 244 245 if (!original || !posed) return NS_ERROR_FAILURE; 246 247 method_exchangeImplementations(original, posed); 248 249 return NS_OK; 250 251 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 252} 253