Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "NativeMenuSupport.h"
using namespace mozilla;
static io_connect_t gRootPort = MACH_PORT_NULL;
nsToolkit* nsToolkit::gToolkit = nullptr;
nsToolkit::nsToolkit()
: mSleepWakeNotificationRLS(nullptr),
mPowerNotifier{0},
mAllProcessMouseMonitor(nil) {
MOZ_COUNT_CTOR(nsToolkit);
RegisterForSleepWakeNotifications();
}
nsToolkit::~nsToolkit() {
MOZ_COUNT_DTOR(nsToolkit);
RemoveSleepWakeNotifications();
StopMonitoringAllProcessMouseEvents();
}
void nsToolkit::PostSleepWakeNotification(const char* aNotification) {
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
if (observerService)
observerService->NotifyObservers(nullptr, aNotification, nullptr);
}
static void ToolkitSleepWakeCallback(void* refCon, io_service_t service,
natural_t messageType,
void* messageArgument) {
NS_OBJC_BEGIN_TRY_IGNORE_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_IGNORE_BLOCK;
}
nsresult nsToolkit::RegisterForSleepWakeNotifications() {
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
IONotificationPortRef notifyPortRef;
NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
gRootPort = ::IORegisterForSystemPower(
0, &notifyPortRef, 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_BLOCK_RETURN(NS_ERROR_FAILURE);
}
void nsToolkit::RemoveSleepWakeNotifications() {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
if (mSleepWakeNotificationRLS) {
::IODeregisterForSystemPower(&mPowerNotifier);
::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mSleepWakeNotificationRLS,
kCFRunLoopDefaultMode);
mSleepWakeNotificationRLS = nullptr;
}
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
// 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::MonitorAllProcessMouseEvents() {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
if (mozilla::widget::NativeMenuSupport::ShouldUseNativeContextMenus()) {
// Don't do this if we are using native context menus.
return;
}
if (getenv("MOZ_NO_GLOBAL_MOUSE_MONITOR")) return;
if (mAllProcessMouseMonitor == nil) {
mAllProcessMouseMonitor = [NSEvent
addGlobalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown |
NSEventMaskLeftMouseDown
handler:^(NSEvent* evt) {
if ([NSApp isActive]) {
return;
}
nsIRollupListener* rollupListener =
nsBaseWidget::
GetActiveRollupListener();
if (!rollupListener) {
return;
}
nsCOMPtr<nsIWidget> rollupWidget =
rollupListener->GetRollupWidget();
if (!rollupWidget) {
return;
}
NSWindow* ctxMenuWindow =
(NSWindow*)
rollupWidget->GetNativeData(
NS_NATIVE_WINDOW);
if (!ctxMenuWindow) {
return;
}
// Don't roll up the rollup widget if
// our mouseDown happens over it (doing
// so would break the corresponding
// context menu).
NSPoint screenLocation =
[NSEvent mouseLocation];
if (NSPointInRect(
screenLocation,
[ctxMenuWindow frame])) {
return;
}
rollupListener->Rollup({});
}];
}
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
void nsToolkit::StopMonitoringAllProcessMouseEvents() {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
if (mAllProcessMouseMonitor != nil) {
[NSEvent removeMonitor:mAllProcessMouseMonitor];
mAllProcessMouseMonitor = nil;
}
NS_OBJC_END_TRY_IGNORE_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_BLOCK_RETURN;
if (!gToolkit) {
gToolkit = new nsToolkit();
}
return gToolkit;
NS_OBJC_END_TRY_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
// 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).
//
// 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_BLOCK_RETURN;
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;
method_exchangeImplementations(original, posed);
return NS_OK;
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}