Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
6 #include "nsToolkit.h"
8 #include <ctype.h>
9 #include <stdlib.h>
10 #include <stdio.h>
12 #include <mach/mach_port.h>
13 #include <mach/mach_interface.h>
14 #include <mach/mach_init.h>
16 extern "C" {
17 #include <mach-o/getsect.h>
18 }
19 #include <unistd.h>
20 #include <dlfcn.h>
22 #import <Cocoa/Cocoa.h>
23 #import <IOKit/pwr_mgt/IOPMLib.h>
24 #import <IOKit/IOMessage.h>
26 #include "nsCocoaUtils.h"
27 #include "nsObjCExceptions.h"
29 #include "nsGkAtoms.h"
30 #include "nsIRollupListener.h"
31 #include "nsIWidget.h"
32 #include "nsBaseWidget.h"
34 #include "nsIObserverService.h"
35 #include "nsIServiceManager.h"
37 #include "mozilla/Preferences.h"
39 using namespace mozilla;
41 static io_connect_t gRootPort = MACH_PORT_NULL;
43 nsToolkit* nsToolkit::gToolkit = nullptr;
45 nsToolkit::nsToolkit()
46 : mSleepWakeNotificationRLS(nullptr)
47 , mEventTapPort(nullptr)
48 , mEventTapRLS(nullptr)
49 {
50 MOZ_COUNT_CTOR(nsToolkit);
51 RegisterForSleepWakeNotifcations();
52 }
54 nsToolkit::~nsToolkit()
55 {
56 MOZ_COUNT_DTOR(nsToolkit);
57 RemoveSleepWakeNotifcations();
58 UnregisterAllProcessMouseEventHandlers();
59 }
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 }
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;
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;
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;
91 case kIOMessageSystemHasPoweredOn:
92 // Handle wakeup.
93 nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC);
94 break;
95 }
97 NS_OBJC_END_TRY_ABORT_BLOCK;
98 }
100 nsresult
101 nsToolkit::RegisterForSleepWakeNotifcations()
102 {
103 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
105 IONotificationPortRef notifyPortRef;
107 NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
109 gRootPort = ::IORegisterForSystemPower(0, ¬ifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
110 if (gRootPort == MACH_PORT_NULL) {
111 NS_ERROR("IORegisterForSystemPower failed");
112 return NS_ERROR_FAILURE;
113 }
115 mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef);
116 ::CFRunLoopAddSource(::CFRunLoopGetCurrent(),
117 mSleepWakeNotificationRLS,
118 kCFRunLoopDefaultMode);
120 return NS_OK;
122 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
123 }
125 void
126 nsToolkit::RemoveSleepWakeNotifcations()
127 {
128 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
130 if (mSleepWakeNotificationRLS) {
131 ::IODeregisterForSystemPower(&mPowerNotifier);
132 ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(),
133 mSleepWakeNotificationRLS,
134 kCFRunLoopDefaultMode);
136 mSleepWakeNotificationRLS = nullptr;
137 }
139 NS_OBJC_END_TRY_ABORT_BLOCK;
140 }
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 }
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;
160 if ((type == kCGEventTapDisabledByUserInput) ||
161 (type == kCGEventTapDisabledByTimeout))
162 return event;
163 if ([NSApp isActive])
164 return event;
166 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
167 NS_ENSURE_TRUE(rollupListener, event);
168 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
169 if (!rollupWidget)
170 return event;
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;
190 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL);
191 }
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;
205 if (getenv("MOZ_DEBUG"))
206 return;
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 */
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 }
244 NS_OBJC_END_TRY_ABORT_BLOCK;
245 }
247 void
248 nsToolkit::UnregisterAllProcessMouseEventHandlers()
249 {
250 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
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 }
267 NS_OBJC_END_TRY_ABORT_BLOCK;
268 }
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;
277 if (!gToolkit) {
278 gToolkit = new nsToolkit();
279 }
281 return gToolkit;
283 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
284 }
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;
306 Method original = nil;
307 Method posed = nil;
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 }
317 if (!original || !posed)
318 return NS_ERROR_FAILURE;
320 method_exchangeImplementations(original, posed);
322 return NS_OK;
324 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
325 }