Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / cocoa / nsToolkit.mm
blob065100a10728ba76a69e705eb95c5bc2653a4dd2
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>
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"
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),
49       mPowerNotifier{0},
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();
63   if (observerService)
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);
78       break;
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);
87       break;
89     case kIOMessageSystemHasPoweredOn:
90       // Handle wakeup.
91       nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC);
92       break;
93   }
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, &notifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
107   if (gRootPort == MACH_PORT_NULL) {
108     NS_ERROR("IORegisterForSystemPower failed");
109     return NS_ERROR_FAILURE;
110   }
112   mSleepWakeNotificationRLS =
113       ::IONotificationPortGetRunLoopSource(notifyPortRef);
114   ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mSleepWakeNotificationRLS,
115                        kCFRunLoopDefaultMode);
117   return NS_OK;
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;
131   }
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
142 // bug 339945).
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.
148     return;
149   }
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]) {
159                                           return;
160                                         }
162                                         nsIRollupListener* rollupListener =
163                                             nsBaseWidget::
164                                                 GetActiveRollupListener();
165                                         if (!rollupListener) {
166                                           return;
167                                         }
169                                         nsCOMPtr<nsIWidget> rollupWidget =
170                                             rollupListener->GetRollupWidget();
171                                         if (!rollupWidget) {
172                                           return;
173                                         }
175                                         NSWindow* ctxMenuWindow =
176                                             (NSWindow*)
177                                                 rollupWidget->GetNativeData(
178                                                     NS_NATIVE_WINDOW);
179                                         if (!ctxMenuWindow) {
180                                           return;
181                                         }
183                                         // Don't roll up the rollup widget if
184                                         // our mouseDown happens over it (doing
185                                         // so would break the corresponding
186                                         // context menu).
187                                         NSPoint screenLocation =
188                                             [NSEvent mouseLocation];
189                                         if (NSPointInRect(
190                                                 screenLocation,
191                                                 [ctxMenuWindow frame])) {
192                                           return;
193                                         }
195                                         rollupListener->Rollup({});
196                                       }];
197   }
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;
208   }
210   NS_OBJC_END_TRY_IGNORE_BLOCK;
213 // Return the nsToolkit instance.  If a toolkit does not yet exist, then one
214 // will be created.
215 // static
216 nsToolkit* nsToolkit::GetToolkit() {
217   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
219   if (!gToolkit) {
220     gToolkit = new nsToolkit();
221   }
223   return gToolkit;
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,
244                                    bool classMethods) {
245   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
247   Method original = nil;
248   Method posed = nil;
250   if (classMethods) {
251     original = class_getClassMethod(aClass, orgMethod);
252     posed = class_getClassMethod(aClass, posedMethod);
253   } else {
254     original = class_getInstanceMethod(aClass, orgMethod);
255     posed = class_getInstanceMethod(aClass, posedMethod);
256   }
258   if (!original || !posed) return NS_ERROR_FAILURE;
260   method_exchangeImplementations(original, posed);
262   return NS_OK;
264   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);