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, &notifyPortRef, 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