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/. */
12 #include <mach/mach_port.h>
13 #include <mach/mach_interface.h>
14 #include <mach/mach_init.h>
17 #include <mach-o/getsect.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"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/Services.h"
39 #include "NativeMenuSupport.h"
41 using namespace mozilla;
43 static io_connect_t gRootPort = MACH_PORT_NULL;
45 nsToolkit* nsToolkit::gToolkit = nullptr;
47 nsToolkit::nsToolkit()
48 : mSleepWakeNotificationRLS(nullptr),
50 mAllProcessMouseMonitor(nil) {
51 MOZ_COUNT_CTOR(nsToolkit);
52 RegisterForSleepWakeNotifications();
55 nsToolkit::~nsToolkit() {
56 MOZ_COUNT_DTOR(nsToolkit);
57 RemoveSleepWakeNotifications();
58 StopMonitoringAllProcessMouseEvents();
61 void nsToolkit::PostSleepWakeNotification(const char* aNotification) {
62 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
64 observerService->NotifyObservers(nullptr, aNotification, nullptr);
67 // http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html
68 static void ToolkitSleepWakeCallback(void* refCon, io_service_t service,
69 natural_t messageType,
70 void* messageArgument) {
71 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
73 switch (messageType) {
74 case kIOMessageSystemWillSleep:
75 // System is going to sleep now.
76 nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC);
77 ::IOAllowPowerChange(gRootPort, (long)messageArgument);
80 case kIOMessageCanSystemSleep:
81 // In this case, the computer has been idle for several minutes
82 // and will sleep soon so you must either allow or cancel
83 // this notification. Important: if you don’t respond, there will
84 // be a 30-second timeout before the computer sleeps.
85 // In Mozilla's case, we always allow sleep.
86 ::IOAllowPowerChange(gRootPort, (long)messageArgument);
89 case kIOMessageSystemHasPoweredOn:
91 nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC);
95 NS_OBJC_END_TRY_IGNORE_BLOCK;
98 nsresult nsToolkit::RegisterForSleepWakeNotifications() {
99 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
101 IONotificationPortRef notifyPortRef;
103 NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
105 gRootPort = ::IORegisterForSystemPower(
106 0, ¬ifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
107 if (gRootPort == MACH_PORT_NULL) {
108 NS_ERROR("IORegisterForSystemPower failed");
109 return NS_ERROR_FAILURE;
112 mSleepWakeNotificationRLS =
113 ::IONotificationPortGetRunLoopSource(notifyPortRef);
114 ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mSleepWakeNotificationRLS,
115 kCFRunLoopDefaultMode);
119 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
122 void nsToolkit::RemoveSleepWakeNotifications() {
123 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
125 if (mSleepWakeNotificationRLS) {
126 ::IODeregisterForSystemPower(&mPowerNotifier);
127 ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mSleepWakeNotificationRLS,
128 kCFRunLoopDefaultMode);
130 mSleepWakeNotificationRLS = nullptr;
133 NS_OBJC_END_TRY_IGNORE_BLOCK;
136 // Cocoa Firefox's use of custom context menus requires that we explicitly
137 // handle mouse events from other processes that the OS handles
138 // "automatically" for native context menus -- mouseMoved events so that
139 // right-click context menus work properly when our browser doesn't have the
140 // focus (bmo bug 368077), and mouseDown events so that our browser can
141 // dismiss a context menu when a mouseDown happens in another process (bmo
143 void nsToolkit::MonitorAllProcessMouseEvents() {
144 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
146 if (mozilla::widget::NativeMenuSupport::ShouldUseNativeContextMenus()) {
147 // Don't do this if we are using native context menus.
151 if (getenv("MOZ_NO_GLOBAL_MOUSE_MONITOR")) return;
153 if (mAllProcessMouseMonitor == nil) {
154 mAllProcessMouseMonitor = [NSEvent
155 addGlobalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown |
156 NSEventMaskLeftMouseDown
157 handler:^(NSEvent* evt) {
158 if ([NSApp isActive]) {
162 nsIRollupListener* rollupListener =
164 GetActiveRollupListener();
165 if (!rollupListener) {
169 nsCOMPtr<nsIWidget> rollupWidget =
170 rollupListener->GetRollupWidget();
175 NSWindow* ctxMenuWindow =
177 rollupWidget->GetNativeData(
179 if (!ctxMenuWindow) {
183 // Don't roll up the rollup widget if
184 // our mouseDown happens over it (doing
185 // so would break the corresponding
187 NSPoint screenLocation =
188 [NSEvent mouseLocation];
191 [ctxMenuWindow frame])) {
195 rollupListener->Rollup({});
199 NS_OBJC_END_TRY_IGNORE_BLOCK;
202 void nsToolkit::StopMonitoringAllProcessMouseEvents() {
203 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
205 if (mAllProcessMouseMonitor != nil) {
206 [NSEvent removeMonitor:mAllProcessMouseMonitor];
207 mAllProcessMouseMonitor = nil;
210 NS_OBJC_END_TRY_IGNORE_BLOCK;
213 // Return the nsToolkit instance. If a toolkit does not yet exist, then one
216 nsToolkit* nsToolkit::GetToolkit() {
217 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
220 gToolkit = new nsToolkit();
225 NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
228 // An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X
229 // Leopard and is available to 64-bit binaries on Leopard and above. Based on
230 // ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling.
231 // Since the Method type becomes an opaque type as of Objective-C 2.0, we'll
232 // have to switch to using accessor methods like
233 // method_exchangeImplementations() when we build 64-bit binaries that use
234 // Objective-C 2.0 (on and for Leopard and above).
236 // Be aware that, if aClass doesn't have an orgMethod selector but one of its
237 // superclasses does, the method substitution will (in effect) take place in
238 // that superclass (rather than in aClass itself). The substitution has
239 // effect on the class where it takes place and all of that class's
240 // subclasses. In order for method swizzling to work properly, posedMethod
241 // needs to be unique in the class where the substitution takes place and all
242 // of its subclasses.
243 nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
245 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
247 Method original = nil;
251 original = class_getClassMethod(aClass, orgMethod);
252 posed = class_getClassMethod(aClass, posedMethod);
254 original = class_getInstanceMethod(aClass, orgMethod);
255 posed = class_getInstanceMethod(aClass, posedMethod);
258 if (!original || !posed) return NS_ERROR_FAILURE;
260 method_exchangeImplementations(original, posed);
264 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);