|
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 |
|
16 extern "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 #include "nsIServiceManager.h" |
|
36 |
|
37 #include "mozilla/Preferences.h" |
|
38 |
|
39 using namespace mozilla; |
|
40 |
|
41 static io_connect_t gRootPort = MACH_PORT_NULL; |
|
42 |
|
43 nsToolkit* nsToolkit::gToolkit = nullptr; |
|
44 |
|
45 nsToolkit::nsToolkit() |
|
46 : mSleepWakeNotificationRLS(nullptr) |
|
47 , mEventTapPort(nullptr) |
|
48 , mEventTapRLS(nullptr) |
|
49 { |
|
50 MOZ_COUNT_CTOR(nsToolkit); |
|
51 RegisterForSleepWakeNotifcations(); |
|
52 } |
|
53 |
|
54 nsToolkit::~nsToolkit() |
|
55 { |
|
56 MOZ_COUNT_DTOR(nsToolkit); |
|
57 RemoveSleepWakeNotifcations(); |
|
58 UnregisterAllProcessMouseEventHandlers(); |
|
59 } |
|
60 |
|
61 void |
|
62 nsToolkit::PostSleepWakeNotification(const char* aNotification) |
|
63 { |
|
64 nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1"); |
|
65 if (observerService) |
|
66 observerService->NotifyObservers(nullptr, aNotification, nullptr); |
|
67 } |
|
68 |
|
69 // http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html |
|
70 static void ToolkitSleepWakeCallback(void *refCon, io_service_t service, natural_t messageType, void * messageArgument) |
|
71 { |
|
72 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
73 |
|
74 switch (messageType) |
|
75 { |
|
76 case kIOMessageSystemWillSleep: |
|
77 // System is going to sleep now. |
|
78 nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC); |
|
79 ::IOAllowPowerChange(gRootPort, (long)messageArgument); |
|
80 break; |
|
81 |
|
82 case kIOMessageCanSystemSleep: |
|
83 // In this case, the computer has been idle for several minutes |
|
84 // and will sleep soon so you must either allow or cancel |
|
85 // this notification. Important: if you don’t respond, there will |
|
86 // be a 30-second timeout before the computer sleeps. |
|
87 // In Mozilla's case, we always allow sleep. |
|
88 ::IOAllowPowerChange(gRootPort,(long)messageArgument); |
|
89 break; |
|
90 |
|
91 case kIOMessageSystemHasPoweredOn: |
|
92 // Handle wakeup. |
|
93 nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC); |
|
94 break; |
|
95 } |
|
96 |
|
97 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
98 } |
|
99 |
|
100 nsresult |
|
101 nsToolkit::RegisterForSleepWakeNotifcations() |
|
102 { |
|
103 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; |
|
104 |
|
105 IONotificationPortRef notifyPortRef; |
|
106 |
|
107 NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake"); |
|
108 |
|
109 gRootPort = ::IORegisterForSystemPower(0, ¬ifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier); |
|
110 if (gRootPort == MACH_PORT_NULL) { |
|
111 NS_ERROR("IORegisterForSystemPower failed"); |
|
112 return NS_ERROR_FAILURE; |
|
113 } |
|
114 |
|
115 mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef); |
|
116 ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), |
|
117 mSleepWakeNotificationRLS, |
|
118 kCFRunLoopDefaultMode); |
|
119 |
|
120 return NS_OK; |
|
121 |
|
122 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
123 } |
|
124 |
|
125 void |
|
126 nsToolkit::RemoveSleepWakeNotifcations() |
|
127 { |
|
128 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
129 |
|
130 if (mSleepWakeNotificationRLS) { |
|
131 ::IODeregisterForSystemPower(&mPowerNotifier); |
|
132 ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), |
|
133 mSleepWakeNotificationRLS, |
|
134 kCFRunLoopDefaultMode); |
|
135 |
|
136 mSleepWakeNotificationRLS = nullptr; |
|
137 } |
|
138 |
|
139 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
140 } |
|
141 |
|
142 // Converts aPoint from the CoreGraphics "global display coordinate" system |
|
143 // (which includes all displays/screens and has a top-left origin) to its |
|
144 // (presumed) Cocoa counterpart (assumed to be the same as the "screen |
|
145 // coordinates" system), which has a bottom-left origin. |
|
146 static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint) |
|
147 { |
|
148 NSPoint cocoaPoint; |
|
149 cocoaPoint.x = aPoint.x; |
|
150 cocoaPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y); |
|
151 return cocoaPoint; |
|
152 } |
|
153 |
|
154 // Since our event tap is "listen only", events arrive here a little after |
|
155 // they've already been processed. |
|
156 static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) |
|
157 { |
|
158 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
159 |
|
160 if ((type == kCGEventTapDisabledByUserInput) || |
|
161 (type == kCGEventTapDisabledByTimeout)) |
|
162 return event; |
|
163 if ([NSApp isActive]) |
|
164 return event; |
|
165 |
|
166 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); |
|
167 NS_ENSURE_TRUE(rollupListener, event); |
|
168 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); |
|
169 if (!rollupWidget) |
|
170 return event; |
|
171 |
|
172 // Don't bother with rightMouseDown events here -- because of the delay, |
|
173 // we'll end up closing browser context menus that we just opened. Since |
|
174 // these events usually raise a context menu, we'll handle them by hooking |
|
175 // the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed |
|
176 // notification (in nsAppShell.mm's AppShellDelegate). |
|
177 if (type == kCGEventRightMouseDown) |
|
178 return event; |
|
179 NSWindow *ctxMenuWindow = (NSWindow*) rollupWidget->GetNativeData(NS_NATIVE_WINDOW); |
|
180 if (!ctxMenuWindow) |
|
181 return event; |
|
182 NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event)); |
|
183 // Don't roll up the rollup widget if our mouseDown happens over it (doing |
|
184 // so would break the corresponding context menu). |
|
185 if (NSPointInRect(screenLocation, [ctxMenuWindow frame])) |
|
186 return event; |
|
187 rollupListener->Rollup(0, nullptr, nullptr); |
|
188 return event; |
|
189 |
|
190 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL); |
|
191 } |
|
192 |
|
193 // Cocoa Firefox's use of custom context menus requires that we explicitly |
|
194 // handle mouse events from other processes that the OS handles |
|
195 // "automatically" for native context menus -- mouseMoved events so that |
|
196 // right-click context menus work properly when our browser doesn't have the |
|
197 // focus (bmo bug 368077), and mouseDown events so that our browser can |
|
198 // dismiss a context menu when a mouseDown happens in another process (bmo |
|
199 // bug 339945). |
|
200 void |
|
201 nsToolkit::RegisterForAllProcessMouseEvents() |
|
202 { |
|
203 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
204 |
|
205 if (getenv("MOZ_DEBUG")) |
|
206 return; |
|
207 |
|
208 // Don't do this for apps that (like Camino) use native context menus. |
|
209 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS |
|
210 return; |
|
211 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */ |
|
212 |
|
213 if (!mEventTapRLS) { |
|
214 // Using an event tap for mouseDown events (instead of installing a |
|
215 // handler for them on the EventMonitor target) works around an Apple |
|
216 // bug that causes OS menus (like the Clock menu) not to work properly |
|
217 // on OS X 10.4.X and below (bmo bug 381448). |
|
218 // We install our event tap "listen only" to get around yet another Apple |
|
219 // bug -- when we install it as an event filter on any kind of mouseDown |
|
220 // event, that kind of event stops working in the main menu, and usually |
|
221 // mouse event processing stops working in all apps in the current login |
|
222 // session (so the entire OS appears to be hung)! The downside of |
|
223 // installing listen-only is that events arrive at our handler slightly |
|
224 // after they've already been processed. |
|
225 mEventTapPort = CGEventTapCreate(kCGSessionEventTap, |
|
226 kCGHeadInsertEventTap, |
|
227 kCGEventTapOptionListenOnly, |
|
228 CGEventMaskBit(kCGEventLeftMouseDown) |
|
229 | CGEventMaskBit(kCGEventRightMouseDown) |
|
230 | CGEventMaskBit(kCGEventOtherMouseDown), |
|
231 EventTapCallback, |
|
232 nullptr); |
|
233 if (!mEventTapPort) |
|
234 return; |
|
235 mEventTapRLS = CFMachPortCreateRunLoopSource(nullptr, mEventTapPort, 0); |
|
236 if (!mEventTapRLS) { |
|
237 CFRelease(mEventTapPort); |
|
238 mEventTapPort = nullptr; |
|
239 return; |
|
240 } |
|
241 CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode); |
|
242 } |
|
243 |
|
244 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
245 } |
|
246 |
|
247 void |
|
248 nsToolkit::UnregisterAllProcessMouseEventHandlers() |
|
249 { |
|
250 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
251 |
|
252 if (mEventTapRLS) { |
|
253 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS, |
|
254 kCFRunLoopDefaultMode); |
|
255 CFRelease(mEventTapRLS); |
|
256 mEventTapRLS = nullptr; |
|
257 } |
|
258 if (mEventTapPort) { |
|
259 // mEventTapPort must be invalidated as well as released. Otherwise the |
|
260 // event tap doesn't get destroyed until the browser process ends (it |
|
261 // keeps showing up in the list returned by CGGetEventTapList()). |
|
262 CFMachPortInvalidate(mEventTapPort); |
|
263 CFRelease(mEventTapPort); |
|
264 mEventTapPort = nullptr; |
|
265 } |
|
266 |
|
267 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
268 } |
|
269 |
|
270 // Return the nsToolkit instance. If a toolkit does not yet exist, then one |
|
271 // will be created. |
|
272 // static |
|
273 nsToolkit* nsToolkit::GetToolkit() |
|
274 { |
|
275 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
276 |
|
277 if (!gToolkit) { |
|
278 gToolkit = new nsToolkit(); |
|
279 } |
|
280 |
|
281 return gToolkit; |
|
282 |
|
283 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr); |
|
284 } |
|
285 |
|
286 // An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X |
|
287 // Leopard and is available to 64-bit binaries on Leopard and above. Based on |
|
288 // ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling. |
|
289 // Since the Method type becomes an opaque type as of Objective-C 2.0, we'll |
|
290 // have to switch to using accessor methods like method_exchangeImplementations() |
|
291 // when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard |
|
292 // and above). |
|
293 // |
|
294 // Be aware that, if aClass doesn't have an orgMethod selector but one of its |
|
295 // superclasses does, the method substitution will (in effect) take place in |
|
296 // that superclass (rather than in aClass itself). The substitution has |
|
297 // effect on the class where it takes place and all of that class's |
|
298 // subclasses. In order for method swizzling to work properly, posedMethod |
|
299 // needs to be unique in the class where the substitution takes place and all |
|
300 // of its subclasses. |
|
301 nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod, |
|
302 bool classMethods) |
|
303 { |
|
304 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; |
|
305 |
|
306 Method original = nil; |
|
307 Method posed = nil; |
|
308 |
|
309 if (classMethods) { |
|
310 original = class_getClassMethod(aClass, orgMethod); |
|
311 posed = class_getClassMethod(aClass, posedMethod); |
|
312 } else { |
|
313 original = class_getInstanceMethod(aClass, orgMethod); |
|
314 posed = class_getInstanceMethod(aClass, posedMethod); |
|
315 } |
|
316 |
|
317 if (!original || !posed) |
|
318 return NS_ERROR_FAILURE; |
|
319 |
|
320 method_exchangeImplementations(original, posed); |
|
321 |
|
322 return NS_OK; |
|
323 |
|
324 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
325 } |