widget/cocoa/nsToolkit.mm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/widget/cocoa/nsToolkit.mm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,325 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "nsToolkit.h"
    1.10 +
    1.11 +#include <ctype.h>
    1.12 +#include <stdlib.h>
    1.13 +#include <stdio.h>
    1.14 +
    1.15 +#include <mach/mach_port.h>
    1.16 +#include <mach/mach_interface.h>
    1.17 +#include <mach/mach_init.h>
    1.18 +
    1.19 +extern "C" {
    1.20 +#include <mach-o/getsect.h>
    1.21 +}
    1.22 +#include <unistd.h>
    1.23 +#include <dlfcn.h>
    1.24 +
    1.25 +#import <Cocoa/Cocoa.h>
    1.26 +#import <IOKit/pwr_mgt/IOPMLib.h>
    1.27 +#import <IOKit/IOMessage.h>
    1.28 +
    1.29 +#include "nsCocoaUtils.h"
    1.30 +#include "nsObjCExceptions.h"
    1.31 +
    1.32 +#include "nsGkAtoms.h"
    1.33 +#include "nsIRollupListener.h"
    1.34 +#include "nsIWidget.h"
    1.35 +#include "nsBaseWidget.h"
    1.36 +
    1.37 +#include "nsIObserverService.h"
    1.38 +#include "nsIServiceManager.h"
    1.39 +
    1.40 +#include "mozilla/Preferences.h"
    1.41 +
    1.42 +using namespace mozilla;
    1.43 +
    1.44 +static io_connect_t gRootPort = MACH_PORT_NULL;
    1.45 +
    1.46 +nsToolkit* nsToolkit::gToolkit = nullptr;
    1.47 +
    1.48 +nsToolkit::nsToolkit()
    1.49 +: mSleepWakeNotificationRLS(nullptr)
    1.50 +, mEventTapPort(nullptr)
    1.51 +, mEventTapRLS(nullptr)
    1.52 +{
    1.53 +  MOZ_COUNT_CTOR(nsToolkit);
    1.54 +  RegisterForSleepWakeNotifcations();
    1.55 +}
    1.56 +
    1.57 +nsToolkit::~nsToolkit()
    1.58 +{
    1.59 +  MOZ_COUNT_DTOR(nsToolkit);
    1.60 +  RemoveSleepWakeNotifcations();
    1.61 +  UnregisterAllProcessMouseEventHandlers();
    1.62 +}
    1.63 +
    1.64 +void
    1.65 +nsToolkit::PostSleepWakeNotification(const char* aNotification)
    1.66 +{
    1.67 +  nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
    1.68 +  if (observerService)
    1.69 +    observerService->NotifyObservers(nullptr, aNotification, nullptr);
    1.70 +}
    1.71 +
    1.72 +// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html
    1.73 +static void ToolkitSleepWakeCallback(void *refCon, io_service_t service, natural_t messageType, void * messageArgument)
    1.74 +{
    1.75 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
    1.76 +
    1.77 +  switch (messageType)
    1.78 +  {
    1.79 +    case kIOMessageSystemWillSleep:
    1.80 +      // System is going to sleep now.
    1.81 +      nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC);
    1.82 +      ::IOAllowPowerChange(gRootPort, (long)messageArgument);
    1.83 +      break;
    1.84 +      
    1.85 +    case kIOMessageCanSystemSleep:
    1.86 +      // In this case, the computer has been idle for several minutes
    1.87 +      // and will sleep soon so you must either allow or cancel
    1.88 +      // this notification. Important: if you don’t respond, there will
    1.89 +      // be a 30-second timeout before the computer sleeps.
    1.90 +      // In Mozilla's case, we always allow sleep.
    1.91 +      ::IOAllowPowerChange(gRootPort,(long)messageArgument);
    1.92 +      break;
    1.93 +      
    1.94 +    case kIOMessageSystemHasPoweredOn:
    1.95 +      // Handle wakeup.
    1.96 +      nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC);
    1.97 +      break;
    1.98 +  }
    1.99 +
   1.100 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.101 +}
   1.102 +
   1.103 +nsresult
   1.104 +nsToolkit::RegisterForSleepWakeNotifcations()
   1.105 +{
   1.106 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   1.107 +
   1.108 +  IONotificationPortRef notifyPortRef;
   1.109 +
   1.110 +  NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
   1.111 +
   1.112 +  gRootPort = ::IORegisterForSystemPower(0, &notifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
   1.113 +  if (gRootPort == MACH_PORT_NULL) {
   1.114 +    NS_ERROR("IORegisterForSystemPower failed");
   1.115 +    return NS_ERROR_FAILURE;
   1.116 +  }
   1.117 +
   1.118 +  mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef);
   1.119 +  ::CFRunLoopAddSource(::CFRunLoopGetCurrent(),
   1.120 +                       mSleepWakeNotificationRLS,
   1.121 +                       kCFRunLoopDefaultMode);
   1.122 +
   1.123 +  return NS_OK;
   1.124 +
   1.125 +  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   1.126 +}
   1.127 +
   1.128 +void
   1.129 +nsToolkit::RemoveSleepWakeNotifcations()
   1.130 +{
   1.131 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.132 +
   1.133 +  if (mSleepWakeNotificationRLS) {
   1.134 +    ::IODeregisterForSystemPower(&mPowerNotifier);
   1.135 +    ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(),
   1.136 +                            mSleepWakeNotificationRLS,
   1.137 +                            kCFRunLoopDefaultMode);
   1.138 +
   1.139 +    mSleepWakeNotificationRLS = nullptr;
   1.140 +  }
   1.141 +
   1.142 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.143 +}
   1.144 +
   1.145 +// Converts aPoint from the CoreGraphics "global display coordinate" system
   1.146 +// (which includes all displays/screens and has a top-left origin) to its
   1.147 +// (presumed) Cocoa counterpart (assumed to be the same as the "screen
   1.148 +// coordinates" system), which has a bottom-left origin.
   1.149 +static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint)
   1.150 +{
   1.151 +  NSPoint cocoaPoint;
   1.152 +  cocoaPoint.x = aPoint.x;
   1.153 +  cocoaPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
   1.154 +  return cocoaPoint;
   1.155 +}
   1.156 +
   1.157 +// Since our event tap is "listen only", events arrive here a little after
   1.158 +// they've already been processed.
   1.159 +static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
   1.160 +{
   1.161 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
   1.162 +
   1.163 +  if ((type == kCGEventTapDisabledByUserInput) ||
   1.164 +      (type == kCGEventTapDisabledByTimeout))
   1.165 +    return event;
   1.166 +  if ([NSApp isActive])
   1.167 +    return event;
   1.168 +
   1.169 +  nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
   1.170 +  NS_ENSURE_TRUE(rollupListener, event);
   1.171 +  nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
   1.172 +  if (!rollupWidget)
   1.173 +    return event;
   1.174 +
   1.175 +  // Don't bother with rightMouseDown events here -- because of the delay,
   1.176 +  // we'll end up closing browser context menus that we just opened.  Since
   1.177 +  // these events usually raise a context menu, we'll handle them by hooking
   1.178 +  // the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed
   1.179 +  // notification (in nsAppShell.mm's AppShellDelegate).
   1.180 +  if (type == kCGEventRightMouseDown)
   1.181 +    return event;
   1.182 +  NSWindow *ctxMenuWindow = (NSWindow*) rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
   1.183 +  if (!ctxMenuWindow)
   1.184 +    return event;
   1.185 +  NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event));
   1.186 +  // Don't roll up the rollup widget if our mouseDown happens over it (doing
   1.187 +  // so would break the corresponding context menu).
   1.188 +  if (NSPointInRect(screenLocation, [ctxMenuWindow frame]))
   1.189 +    return event;
   1.190 +  rollupListener->Rollup(0, nullptr, nullptr);
   1.191 +  return event;
   1.192 +
   1.193 +  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL);
   1.194 +}
   1.195 +
   1.196 +// Cocoa Firefox's use of custom context menus requires that we explicitly
   1.197 +// handle mouse events from other processes that the OS handles
   1.198 +// "automatically" for native context menus -- mouseMoved events so that
   1.199 +// right-click context menus work properly when our browser doesn't have the
   1.200 +// focus (bmo bug 368077), and mouseDown events so that our browser can
   1.201 +// dismiss a context menu when a mouseDown happens in another process (bmo
   1.202 +// bug 339945).
   1.203 +void
   1.204 +nsToolkit::RegisterForAllProcessMouseEvents()
   1.205 +{
   1.206 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.207 +
   1.208 +  if (getenv("MOZ_DEBUG"))
   1.209 +    return;
   1.210 +
   1.211 +  // Don't do this for apps that (like Camino) use native context menus.
   1.212 +#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
   1.213 +  return;
   1.214 +#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
   1.215 +
   1.216 +  if (!mEventTapRLS) {
   1.217 +    // Using an event tap for mouseDown events (instead of installing a
   1.218 +    // handler for them on the EventMonitor target) works around an Apple
   1.219 +    // bug that causes OS menus (like the Clock menu) not to work properly
   1.220 +    // on OS X 10.4.X and below (bmo bug 381448).
   1.221 +    // We install our event tap "listen only" to get around yet another Apple
   1.222 +    // bug -- when we install it as an event filter on any kind of mouseDown
   1.223 +    // event, that kind of event stops working in the main menu, and usually
   1.224 +    // mouse event processing stops working in all apps in the current login
   1.225 +    // session (so the entire OS appears to be hung)!  The downside of
   1.226 +    // installing listen-only is that events arrive at our handler slightly
   1.227 +    // after they've already been processed.
   1.228 +    mEventTapPort = CGEventTapCreate(kCGSessionEventTap,
   1.229 +                                     kCGHeadInsertEventTap,
   1.230 +                                     kCGEventTapOptionListenOnly,
   1.231 +                                     CGEventMaskBit(kCGEventLeftMouseDown)
   1.232 +                                       | CGEventMaskBit(kCGEventRightMouseDown)
   1.233 +                                       | CGEventMaskBit(kCGEventOtherMouseDown),
   1.234 +                                     EventTapCallback,
   1.235 +                                     nullptr);
   1.236 +    if (!mEventTapPort)
   1.237 +      return;
   1.238 +    mEventTapRLS = CFMachPortCreateRunLoopSource(nullptr, mEventTapPort, 0);
   1.239 +    if (!mEventTapRLS) {
   1.240 +      CFRelease(mEventTapPort);
   1.241 +      mEventTapPort = nullptr;
   1.242 +      return;
   1.243 +    }
   1.244 +    CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode);
   1.245 +  }
   1.246 +
   1.247 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.248 +}
   1.249 +
   1.250 +void
   1.251 +nsToolkit::UnregisterAllProcessMouseEventHandlers()
   1.252 +{
   1.253 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   1.254 +
   1.255 +  if (mEventTapRLS) {
   1.256 +    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS,
   1.257 +                          kCFRunLoopDefaultMode);
   1.258 +    CFRelease(mEventTapRLS);
   1.259 +    mEventTapRLS = nullptr;
   1.260 +  }
   1.261 +  if (mEventTapPort) {
   1.262 +    // mEventTapPort must be invalidated as well as released.  Otherwise the
   1.263 +    // event tap doesn't get destroyed until the browser process ends (it
   1.264 +    // keeps showing up in the list returned by CGGetEventTapList()).
   1.265 +    CFMachPortInvalidate(mEventTapPort);
   1.266 +    CFRelease(mEventTapPort);
   1.267 +    mEventTapPort = nullptr;
   1.268 +  }
   1.269 +
   1.270 +  NS_OBJC_END_TRY_ABORT_BLOCK;
   1.271 +}
   1.272 +
   1.273 +// Return the nsToolkit instance.  If a toolkit does not yet exist, then one
   1.274 +// will be created.
   1.275 +// static
   1.276 +nsToolkit* nsToolkit::GetToolkit()
   1.277 +{
   1.278 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
   1.279 +
   1.280 +  if (!gToolkit) {
   1.281 +    gToolkit = new nsToolkit();
   1.282 +  }
   1.283 +
   1.284 +  return gToolkit;
   1.285 +
   1.286 +  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
   1.287 +}
   1.288 +
   1.289 +// An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X
   1.290 +// Leopard and is available to 64-bit binaries on Leopard and above.  Based on
   1.291 +// ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling.
   1.292 +// Since the Method type becomes an opaque type as of Objective-C 2.0, we'll
   1.293 +// have to switch to using accessor methods like method_exchangeImplementations()
   1.294 +// when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard
   1.295 +// and above).
   1.296 +//
   1.297 +// Be aware that, if aClass doesn't have an orgMethod selector but one of its
   1.298 +// superclasses does, the method substitution will (in effect) take place in
   1.299 +// that superclass (rather than in aClass itself).  The substitution has
   1.300 +// effect on the class where it takes place and all of that class's
   1.301 +// subclasses.  In order for method swizzling to work properly, posedMethod
   1.302 +// needs to be unique in the class where the substitution takes place and all
   1.303 +// of its subclasses.
   1.304 +nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
   1.305 +                                   bool classMethods)
   1.306 +{
   1.307 +  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   1.308 +
   1.309 +  Method original = nil;
   1.310 +  Method posed = nil;
   1.311 +
   1.312 +  if (classMethods) {
   1.313 +    original = class_getClassMethod(aClass, orgMethod);
   1.314 +    posed = class_getClassMethod(aClass, posedMethod);
   1.315 +  } else {
   1.316 +    original = class_getInstanceMethod(aClass, orgMethod);
   1.317 +    posed = class_getInstanceMethod(aClass, posedMethod);
   1.318 +  }
   1.319 +
   1.320 +  if (!original || !posed)
   1.321 +    return NS_ERROR_FAILURE;
   1.322 +
   1.323 +  method_exchangeImplementations(original, posed);
   1.324 +
   1.325 +  return NS_OK;
   1.326 +
   1.327 +  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   1.328 +}

mercurial