diff options
Diffstat (limited to 'widget/cocoa/nsToolkit.mm')
-rw-r--r-- | widget/cocoa/nsToolkit.mm | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/widget/cocoa/nsToolkit.mm b/widget/cocoa/nsToolkit.mm new file mode 100644 index 0000000000..0180262067 --- /dev/null +++ b/widget/cocoa/nsToolkit.mm @@ -0,0 +1,252 @@ +/* -*- 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); +} + +// 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_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, ¬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_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 +// 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). +// +// 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); +} |