widget/cocoa/nsToolkit.mm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial