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

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

mercurial