mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-29 21:25:35 +00:00
332 lines
11 KiB
Plaintext
332 lines
11 KiB
Plaintext
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||
|
||
#include "nsToolkit.h"
|
||
|
||
#include <ctype.h>
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
|
||
#include <mach/mach_port.h>
|
||
#include <mach/mach_interface.h>
|
||
#include <mach/mach_init.h>
|
||
|
||
extern "C" {
|
||
#include <mach-o/getsect.h>
|
||
}
|
||
#include <unistd.h>
|
||
#include <dlfcn.h>
|
||
|
||
#import <Cocoa/Cocoa.h>
|
||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||
#import <IOKit/IOMessage.h>
|
||
|
||
#include "nsCocoaUtils.h"
|
||
#include "nsObjCExceptions.h"
|
||
|
||
#include "nsGkAtoms.h"
|
||
#include "nsIRollupListener.h"
|
||
#include "nsIWidget.h"
|
||
#include "nsBaseWidget.h"
|
||
|
||
#include "nsIObserverService.h"
|
||
#include "nsIServiceManager.h"
|
||
|
||
#include "mozilla/Preferences.h"
|
||
|
||
using namespace mozilla;
|
||
|
||
static io_connect_t gRootPort = MACH_PORT_NULL;
|
||
|
||
nsToolkit* nsToolkit::gToolkit = nullptr;
|
||
|
||
nsToolkit::nsToolkit()
|
||
: mSleepWakeNotificationRLS(nullptr)
|
||
, mEventTapPort(nullptr)
|
||
, mEventTapRLS(nullptr)
|
||
{
|
||
MOZ_COUNT_CTOR(nsToolkit);
|
||
RegisterForSleepWakeNotifcations();
|
||
RegisterForAllProcessMouseEvents();
|
||
}
|
||
|
||
nsToolkit::~nsToolkit()
|
||
{
|
||
MOZ_COUNT_DTOR(nsToolkit);
|
||
RemoveSleepWakeNotifcations();
|
||
UnregisterAllProcessMouseEventHandlers();
|
||
}
|
||
|
||
void
|
||
nsToolkit::PostSleepWakeNotification(const char* aNotification)
|
||
{
|
||
nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
|
||
if (observerService)
|
||
observerService->NotifyObservers(nullptr, aNotification, nullptr);
|
||
}
|
||
|
||
// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html
|
||
static void ToolkitSleepWakeCallback(void *refCon, io_service_t service, natural_t messageType, void * messageArgument)
|
||
{
|
||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||
|
||
switch (messageType)
|
||
{
|
||
case kIOMessageSystemWillSleep:
|
||
// System is going to sleep now.
|
||
nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC);
|
||
::IOAllowPowerChange(gRootPort, (long)messageArgument);
|
||
break;
|
||
|
||
case kIOMessageCanSystemSleep:
|
||
// In this case, the computer has been idle for several minutes
|
||
// and will sleep soon so you must either allow or cancel
|
||
// this notification. Important: if you don’t respond, there will
|
||
// be a 30-second timeout before the computer sleeps.
|
||
// In Mozilla's case, we always allow sleep.
|
||
::IOAllowPowerChange(gRootPort,(long)messageArgument);
|
||
break;
|
||
|
||
case kIOMessageSystemHasPoweredOn:
|
||
// Handle wakeup.
|
||
nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC);
|
||
break;
|
||
}
|
||
|
||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||
}
|
||
|
||
nsresult
|
||
nsToolkit::RegisterForSleepWakeNotifcations()
|
||
{
|
||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||
|
||
IONotificationPortRef notifyPortRef;
|
||
|
||
NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
|
||
|
||
gRootPort = ::IORegisterForSystemPower(0, ¬ifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
|
||
if (gRootPort == MACH_PORT_NULL) {
|
||
NS_ERROR("IORegisterForSystemPower failed");
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef);
|
||
::CFRunLoopAddSource(::CFRunLoopGetCurrent(),
|
||
mSleepWakeNotificationRLS,
|
||
kCFRunLoopDefaultMode);
|
||
|
||
return NS_OK;
|
||
|
||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||
}
|
||
|
||
void
|
||
nsToolkit::RemoveSleepWakeNotifcations()
|
||
{
|
||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||
|
||
if (mSleepWakeNotificationRLS) {
|
||
::IODeregisterForSystemPower(&mPowerNotifier);
|
||
::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(),
|
||
mSleepWakeNotificationRLS,
|
||
kCFRunLoopDefaultMode);
|
||
|
||
mSleepWakeNotificationRLS = nullptr;
|
||
}
|
||
|
||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||
}
|
||
|
||
// Converts aPoint from the CoreGraphics "global display coordinate" system
|
||
// (which includes all displays/screens and has a top-left origin) to its
|
||
// (presumed) Cocoa counterpart (assumed to be the same as the "screen
|
||
// coordinates" system), which has a bottom-left origin.
|
||
static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint)
|
||
{
|
||
NSPoint cocoaPoint;
|
||
cocoaPoint.x = aPoint.x;
|
||
cocoaPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
|
||
return cocoaPoint;
|
||
}
|
||
|
||
// Since our event tap is "listen only", events arrive here a little after
|
||
// they've already been processed.
|
||
static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
|
||
{
|
||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
||
|
||
if ((type == kCGEventTapDisabledByUserInput) ||
|
||
(type == kCGEventTapDisabledByTimeout))
|
||
return event;
|
||
if ([NSApp isActive])
|
||
return event;
|
||
|
||
nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
|
||
nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
|
||
if (!rollupWidget)
|
||
return event;
|
||
|
||
// Don't bother with rightMouseDown events here -- because of the delay,
|
||
// we'll end up closing browser context menus that we just opened. Since
|
||
// these events usually raise a context menu, we'll handle them by hooking
|
||
// the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed
|
||
// notification (in nsAppShell.mm's AppShellDelegate).
|
||
if (type == kCGEventRightMouseDown)
|
||
return event;
|
||
NSWindow *ctxMenuWindow = (NSWindow*) rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
|
||
if (!ctxMenuWindow)
|
||
return event;
|
||
NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event));
|
||
// Don't roll up the rollup widget if our mouseDown happens over it (doing
|
||
// so would break the corresponding context menu).
|
||
if (NSPointInRect(screenLocation, [ctxMenuWindow frame]))
|
||
return event;
|
||
rollupListener->Rollup(0, nullptr);
|
||
return event;
|
||
|
||
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL);
|
||
}
|
||
|
||
// Cocoa Firefox's use of custom context menus requires that we explicitly
|
||
// handle mouse events from other processes that the OS handles
|
||
// "automatically" for native context menus -- mouseMoved events so that
|
||
// right-click context menus work properly when our browser doesn't have the
|
||
// focus (bmo bug 368077), and mouseDown events so that our browser can
|
||
// dismiss a context menu when a mouseDown happens in another process (bmo
|
||
// bug 339945).
|
||
void
|
||
nsToolkit::RegisterForAllProcessMouseEvents()
|
||
{
|
||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||
|
||
// Don't do this for apps that (like Camino) use native context menus.
|
||
#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
|
||
return;
|
||
#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
|
||
|
||
if (!mEventTapRLS) {
|
||
// Using an event tap for mouseDown events (instead of installing a
|
||
// handler for them on the EventMonitor target) works around an Apple
|
||
// bug that causes OS menus (like the Clock menu) not to work properly
|
||
// on OS X 10.4.X and below (bmo bug 381448).
|
||
// We install our event tap "listen only" to get around yet another Apple
|
||
// bug -- when we install it as an event filter on any kind of mouseDown
|
||
// event, that kind of event stops working in the main menu, and usually
|
||
// mouse event processing stops working in all apps in the current login
|
||
// session (so the entire OS appears to be hung)! The downside of
|
||
// installing listen-only is that events arrive at our handler slightly
|
||
// after they've already been processed.
|
||
mEventTapPort = CGEventTapCreate(kCGSessionEventTap,
|
||
kCGHeadInsertEventTap,
|
||
kCGEventTapOptionListenOnly,
|
||
CGEventMaskBit(kCGEventLeftMouseDown)
|
||
| CGEventMaskBit(kCGEventRightMouseDown)
|
||
| CGEventMaskBit(kCGEventOtherMouseDown),
|
||
EventTapCallback,
|
||
nullptr);
|
||
if (!mEventTapPort)
|
||
return;
|
||
mEventTapRLS = CFMachPortCreateRunLoopSource(nullptr, mEventTapPort, 0);
|
||
if (!mEventTapRLS) {
|
||
CFRelease(mEventTapPort);
|
||
mEventTapPort = nullptr;
|
||
return;
|
||
}
|
||
CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode);
|
||
}
|
||
|
||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||
}
|
||
|
||
void
|
||
nsToolkit::UnregisterAllProcessMouseEventHandlers()
|
||
{
|
||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||
|
||
if (mEventTapRLS) {
|
||
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS,
|
||
kCFRunLoopDefaultMode);
|
||
CFRelease(mEventTapRLS);
|
||
mEventTapRLS = nullptr;
|
||
}
|
||
if (mEventTapPort) {
|
||
// mEventTapPort must be invalidated as well as released. Otherwise the
|
||
// event tap doesn't get destroyed until the browser process ends (it
|
||
// keeps showing up in the list returned by CGGetEventTapList()).
|
||
CFMachPortInvalidate(mEventTapPort);
|
||
CFRelease(mEventTapPort);
|
||
mEventTapPort = nullptr;
|
||
}
|
||
|
||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||
}
|
||
|
||
// Return the nsToolkit instance. If a toolkit does not yet exist, then one
|
||
// will be created.
|
||
// static
|
||
nsToolkit* nsToolkit::GetToolkit()
|
||
{
|
||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
||
|
||
if (!gToolkit) {
|
||
gToolkit = new nsToolkit();
|
||
}
|
||
|
||
return gToolkit;
|
||
|
||
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
|
||
}
|
||
|
||
// An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X
|
||
// Leopard and is available to 64-bit binaries on Leopard and above. Based on
|
||
// ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling.
|
||
// Since the Method type becomes an opaque type as of Objective-C 2.0, we'll
|
||
// have to switch to using accessor methods like method_exchangeImplementations()
|
||
// when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard
|
||
// and above). But these accessor methods aren't available in Objective-C 1
|
||
// (or on Tiger). So we need to access Method's members directly for (Tiger-
|
||
// capable) binaries (32-bit or 64-bit) that use Objective-C 1 (as long as we
|
||
// keep supporting Tiger).
|
||
//
|
||
// Be aware that, if aClass doesn't have an orgMethod selector but one of its
|
||
// superclasses does, the method substitution will (in effect) take place in
|
||
// that superclass (rather than in aClass itself). The substitution has
|
||
// effect on the class where it takes place and all of that class's
|
||
// subclasses. In order for method swizzling to work properly, posedMethod
|
||
// needs to be unique in the class where the substitution takes place and all
|
||
// of its subclasses.
|
||
nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
|
||
bool classMethods)
|
||
{
|
||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||
|
||
Method original = nil;
|
||
Method posed = nil;
|
||
|
||
if (classMethods) {
|
||
original = class_getClassMethod(aClass, orgMethod);
|
||
posed = class_getClassMethod(aClass, posedMethod);
|
||
} else {
|
||
original = class_getInstanceMethod(aClass, orgMethod);
|
||
posed = class_getInstanceMethod(aClass, posedMethod);
|
||
}
|
||
|
||
if (!original || !posed)
|
||
return NS_ERROR_FAILURE;
|
||
|
||
#ifdef __LP64__
|
||
method_exchangeImplementations(original, posed);
|
||
#else
|
||
IMP aMethodImp = original->method_imp;
|
||
original->method_imp = posed->method_imp;
|
||
posed->method_imp = aMethodImp;
|
||
#endif
|
||
|
||
return NS_OK;
|
||
|
||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||
}
|