diff options
author | Brian Smith <brian@dbsoft.org> | 2022-04-26 09:57:40 -0500 |
---|---|---|
committer | Brian Smith <brian@dbsoft.org> | 2022-04-26 10:19:03 -0500 |
commit | 93407295fc1e294855b7943cb00c00d2d4debc02 (patch) | |
tree | c485b918766c8618123f068740e72463f8acec63 | |
parent | db8d2ef67236c020764c5c01546dacd2c362ddd5 (diff) | |
download | uxp-93407295fc1e294855b7943cb00c00d2d4debc02.tar.gz |
Issue #1829 - Revert “Issue #1751 -- Remove cocoa support code from /dom”
This reverts commit ca35efb84ebae522f9ab7803d8e017f721e03207.
23 files changed, 2294 insertions, 2 deletions
diff --git a/dom/gamepad/cocoa/CocoaGamepad.cpp b/dom/gamepad/cocoa/CocoaGamepad.cpp new file mode 100644 index 0000000000..eb6eda9a17 --- /dev/null +++ b/dom/gamepad/cocoa/CocoaGamepad.cpp @@ -0,0 +1,590 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +// mostly derived from the Allegro source code at: +// http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup + +#include "mozilla/dom/Gamepad.h" +#include "mozilla/dom/GamepadPlatformService.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/hid/IOHIDBase.h> +#include <IOKit/hid/IOHIDKeys.h> +#include <IOKit/hid/IOHIDManager.h> + +#include <stdio.h> +#include <vector> + +namespace { + +using namespace mozilla; +using namespace mozilla::dom; +using std::vector; + +struct Button { + int id; + bool analog; + IOHIDElementRef element; + CFIndex min; + CFIndex max; + + Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax) : + id(aId), + analog((aMax - aMin) > 1), + element(aElement), + min(aMin), + max(aMax) {} +}; + +struct Axis { + int id; + IOHIDElementRef element; + uint32_t usagePage; + uint32_t usage; + CFIndex min; + CFIndex max; +}; + +typedef bool dpad_buttons[4]; + +// These values can be found in the USB HID Usage Tables: +// http://www.usb.org/developers/hidpage +const unsigned kDesktopUsagePage = 0x01; +const unsigned kSimUsagePage = 0x02; +const unsigned kAcceleratorUsage = 0xC4; +const unsigned kBrakeUsage = 0xC5; +const unsigned kJoystickUsage = 0x04; +const unsigned kGamepadUsage = 0x05; +const unsigned kAxisUsageMin = 0x30; +const unsigned kAxisUsageMax = 0x35; +const unsigned kDpadUsage = 0x39; +const unsigned kButtonUsagePage = 0x09; +const unsigned kConsumerPage = 0x0C; +const unsigned kHomeUsage = 0x223; +const unsigned kBackUsage = 0x224; + + +class Gamepad { + private: + IOHIDDeviceRef mDevice; + nsTArray<Button> buttons; + nsTArray<Axis> axes; + IOHIDElementRef mDpad; + dpad_buttons mDpadState; + + public: + Gamepad() : mDevice(nullptr), mDpad(nullptr), mSuperIndex(-1) {} + bool operator==(IOHIDDeviceRef device) const { return mDevice == device; } + bool empty() const { return mDevice == nullptr; } + void clear() + { + mDevice = nullptr; + buttons.Clear(); + axes.Clear(); + mDpad = nullptr; + mSuperIndex = -1; + } + void init(IOHIDDeviceRef device); + size_t numButtons() { return buttons.Length() + (mDpad ? 4 : 0); } + size_t numAxes() { return axes.Length(); } + + // Index given by our superclass. + uint32_t mSuperIndex; + + bool isDpad(IOHIDElementRef element) const + { + return element == mDpad; + } + + const dpad_buttons& getDpadState() const + { + return mDpadState; + } + + void setDpadState(const dpad_buttons& dpadState) + { + for (unsigned i = 0; i < ArrayLength(mDpadState); i++) { + mDpadState[i] = dpadState[i]; + } + } + + const Button* lookupButton(IOHIDElementRef element) const + { + for (unsigned i = 0; i < buttons.Length(); i++) { + if (buttons[i].element == element) + return &buttons[i]; + } + return nullptr; + } + + const Axis* lookupAxis(IOHIDElementRef element) const + { + for (unsigned i = 0; i < axes.Length(); i++) { + if (axes[i].element == element) + return &axes[i]; + } + return nullptr; + } +}; + +class AxisComparator { +public: + bool Equals(const Axis& a1, const Axis& a2) const + { + return a1.usagePage == a2.usagePage && a1.usage == a2.usage; + } + bool LessThan(const Axis& a1, const Axis& a2) const + { + if (a1.usagePage == a2.usagePage) { + return a1.usage < a2.usage; + } + return a1.usagePage < a2.usagePage; + } +}; + +void Gamepad::init(IOHIDDeviceRef device) +{ + clear(); + mDevice = device; + + CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, + nullptr, + kIOHIDOptionsTypeNone); + CFIndex n = CFArrayGetCount(elements); + for (CFIndex i = 0; i < n; i++) { + IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, + i); + uint32_t usagePage = IOHIDElementGetUsagePage(element); + uint32_t usage = IOHIDElementGetUsage(element); + + if (usagePage == kDesktopUsagePage && + usage >= kAxisUsageMin && + usage <= kAxisUsageMax) + { + Axis axis = { int(axes.Length()), + element, + usagePage, + usage, + IOHIDElementGetLogicalMin(element), + IOHIDElementGetLogicalMax(element) }; + axes.AppendElement(axis); + } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage && + // Don't know how to handle d-pads that return weird values. + IOHIDElementGetLogicalMax(element) - IOHIDElementGetLogicalMin(element) == 7) { + mDpad = element; + } else if ((usagePage == kSimUsagePage && + (usage == kAcceleratorUsage || + usage == kBrakeUsage)) || + (usagePage == kButtonUsagePage) || + (usagePage == kConsumerPage && + (usage == kHomeUsage || + usage == kBackUsage))) { + Button button(int(buttons.Length()), element, IOHIDElementGetLogicalMin(element), IOHIDElementGetLogicalMax(element)); + buttons.AppendElement(button); + } else { + //TODO: handle other usage pages + } + } + + AxisComparator comparator; + axes.Sort(comparator); + for (unsigned i = 0; i < axes.Length(); i++) { + axes[i].id = i; + } +} + +class DarwinGamepadService { + private: + IOHIDManagerRef mManager; + vector<Gamepad> mGamepads; + + //Workaround to support running in background thread + CFRunLoopRef mMonitorRunLoop; + nsCOMPtr<nsIThread> mMonitorThread; + + static void DeviceAddedCallback(void* data, IOReturn result, + void* sender, IOHIDDeviceRef device); + static void DeviceRemovedCallback(void* data, IOReturn result, + void* sender, IOHIDDeviceRef device); + static void InputValueChangedCallback(void* data, IOReturn result, + void* sender, IOHIDValueRef newValue); + + void DeviceAdded(IOHIDDeviceRef device); + void DeviceRemoved(IOHIDDeviceRef device); + void InputValueChanged(IOHIDValueRef value); + void StartupInternal(); + + public: + DarwinGamepadService(); + ~DarwinGamepadService(); + void Startup(); + void Shutdown(); + friend class DarwinGamepadServiceStartupRunnable; +}; + +class DarwinGamepadServiceStartupRunnable final : public Runnable +{ + private: + ~DarwinGamepadServiceStartupRunnable() {} + // This Runnable schedules startup of DarwinGamepadService + // in a new thread, pointer to DarwinGamepadService is only + // used by this Runnable within its thread. + DarwinGamepadService MOZ_NON_OWNING_REF *mService; + public: + explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService *service) + : mService(service) {} + NS_IMETHOD Run() override + { + MOZ_ASSERT(mService); + mService->StartupInternal(); + return NS_OK; + } +}; + +void +DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) +{ + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + + size_t slot = size_t(-1); + for (size_t i = 0; i < mGamepads.size(); i++) { + if (mGamepads[i] == device) + return; + if (slot == size_t(-1) && mGamepads[i].empty()) + slot = i; + } + + if (slot == size_t(-1)) { + slot = mGamepads.size(); + mGamepads.push_back(Gamepad()); + } + mGamepads[slot].init(device); + + // Gather some identifying information + CFNumberRef vendorIdRef = + (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)); + CFNumberRef productIdRef = + (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)); + CFStringRef productRef = + (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); + int vendorId, productId; + CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId); + CFNumberGetValue(productIdRef, kCFNumberIntType, &productId); + char product_name[128]; + CFStringGetCString(productRef, product_name, + sizeof(product_name), kCFStringEncodingASCII); + char buffer[256]; + sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name); + uint32_t index = service->AddGamepad(buffer, + mozilla::dom::GamepadMappingType::_empty, + (int)mGamepads[slot].numButtons(), + (int)mGamepads[slot].numAxes()); + mGamepads[slot].mSuperIndex = index; +} + +void +DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) +{ + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + for (size_t i = 0; i < mGamepads.size(); i++) { + if (mGamepads[i] == device) { + service->RemoveGamepad(mGamepads[i].mSuperIndex); + mGamepads[i].clear(); + return; + } + } +} + +/* + * Given a value from a d-pad (POV hat in USB HID terminology), + * represent it as 4 buttons, one for each cardinal direction. + */ +static void +UnpackDpad(int dpad_value, int min, int max, dpad_buttons& buttons) +{ + const unsigned kUp = 0; + const unsigned kDown = 1; + const unsigned kLeft = 2; + const unsigned kRight = 3; + + // Different controllers have different ways of representing + // "nothing is pressed", but they're all outside the range of values. + if (dpad_value < min || dpad_value > max) { + // Nothing is pressed. + return; + } + + // Normalize value to start at 0. + int value = dpad_value - min; + + // Value will be in the range 0-7. The value represents the + // position of the d-pad around a circle, with 0 being straight up, + // 2 being right, 4 being straight down, and 6 being left. + if (value < 2 || value > 6) { + buttons[kUp] = true; + } + if (value > 2 && value < 6) { + buttons[kDown] = true; + } + if (value > 4) { + buttons[kLeft] = true; + } + if (value > 0 && value < 4) { + buttons[kRight] = true; + } +} + +void +DarwinGamepadService::InputValueChanged(IOHIDValueRef value) +{ + RefPtr<GamepadPlatformService> service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + + uint32_t value_length = IOHIDValueGetLength(value); + if (value_length > 4) { + // Workaround for bizarre issue with PS3 controllers that try to return + // massive (30+ byte) values and crash IOHIDValueGetIntegerValue + return; + } + IOHIDElementRef element = IOHIDValueGetElement(value); + IOHIDDeviceRef device = IOHIDElementGetDevice(element); + + for (unsigned i = 0; i < mGamepads.size(); i++) { + Gamepad &gamepad = mGamepads[i]; + if (gamepad == device) { + if (gamepad.isDpad(element)) { + const dpad_buttons& oldState = gamepad.getDpadState(); + dpad_buttons newState = { false, false, false, false }; + UnpackDpad(IOHIDValueGetIntegerValue(value), + IOHIDElementGetLogicalMin(element), + IOHIDElementGetLogicalMax(element), + newState); + const int numButtons = gamepad.numButtons(); + for (unsigned b = 0; b < ArrayLength(newState); b++) { + if (newState[b] != oldState[b]) { + service->NewButtonEvent(gamepad.mSuperIndex, + numButtons - 4 + b, + newState[b]); + } + } + gamepad.setDpadState(newState); + } else if (const Axis* axis = gamepad.lookupAxis(element)) { + double d = IOHIDValueGetIntegerValue(value); + double v = 2.0f * (d - axis->min) / + (double)(axis->max - axis->min) - 1.0f; + service->NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v); + } else if (const Button* button = gamepad.lookupButton(element)) { + int iv = IOHIDValueGetIntegerValue(value); + bool pressed = iv != 0; + double v = 0; + if (button->analog) { + double dv = iv; + v = (dv - button->min) / (double)(button->max - button->min); + } else { + v = pressed ? 1.0 : 0.0; + } + service->NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v); + } + return; + } + } +} + +void +DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result, + void* sender, IOHIDDeviceRef device) +{ + DarwinGamepadService* service = (DarwinGamepadService*)data; + service->DeviceAdded(device); +} + +void +DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result, + void* sender, IOHIDDeviceRef device) +{ + DarwinGamepadService* service = (DarwinGamepadService*)data; + service->DeviceRemoved(device); +} + +void +DarwinGamepadService::InputValueChangedCallback(void* data, + IOReturn result, + void* sender, + IOHIDValueRef newValue) +{ + DarwinGamepadService* service = (DarwinGamepadService*)data; + service->InputValueChanged(newValue); +} + +static CFMutableDictionaryRef +MatchingDictionary(UInt32 inUsagePage, UInt32 inUsage) +{ + CFMutableDictionaryRef dict = + CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (!dict) + return nullptr; + CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, + kCFNumberIntType, + &inUsagePage); + if (!number) { + CFRelease(dict); + return nullptr; + } + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number); + CFRelease(number); + + number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage); + if (!number) { + CFRelease(dict); + return nullptr; + } + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number); + CFRelease(number); + + return dict; +} + +DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {} + +DarwinGamepadService::~DarwinGamepadService() +{ + if (mManager != nullptr) + CFRelease(mManager); +} + +void +DarwinGamepadService::StartupInternal() +{ + if (mManager != nullptr) + return; + + IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, + kIOHIDOptionsTypeNone); + + CFMutableDictionaryRef criteria_arr[2]; + criteria_arr[0] = MatchingDictionary(kDesktopUsagePage, + kJoystickUsage); + if (!criteria_arr[0]) { + CFRelease(manager); + return; + } + + criteria_arr[1] = MatchingDictionary(kDesktopUsagePage, + kGamepadUsage); + if (!criteria_arr[1]) { + CFRelease(criteria_arr[0]); + CFRelease(manager); + return; + } + + CFArrayRef criteria = + CFArrayCreate(kCFAllocatorDefault, (const void**)criteria_arr, 2, nullptr); + if (!criteria) { + CFRelease(criteria_arr[1]); + CFRelease(criteria_arr[0]); + CFRelease(manager); + return; + } + + IOHIDManagerSetDeviceMatchingMultiple(manager, criteria); + CFRelease(criteria); + CFRelease(criteria_arr[1]); + CFRelease(criteria_arr[0]); + + IOHIDManagerRegisterDeviceMatchingCallback(manager, + DeviceAddedCallback, + this); + IOHIDManagerRegisterDeviceRemovalCallback(manager, + DeviceRemovedCallback, + this); + IOHIDManagerRegisterInputValueCallback(manager, + InputValueChangedCallback, + this); + IOHIDManagerScheduleWithRunLoop(manager, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode); + IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone); + if (rv != kIOReturnSuccess) { + CFRelease(manager); + return; + } + + mManager = manager; + + // We held the handle of the CFRunLoop to make sure we + // can shut it down explicitly by CFRunLoopStop in another + // thread. + mMonitorRunLoop = CFRunLoopGetCurrent(); + + // CFRunLoopRun() is a blocking message loop when it's called in + // non-main thread so this thread cannot receive any other runnables + // and nsITimer timeout events after it's called. + CFRunLoopRun(); +} + +void DarwinGamepadService::Startup() +{ + Unused << NS_NewThread(getter_AddRefs(mMonitorThread), + new DarwinGamepadServiceStartupRunnable(this)); +} + +void DarwinGamepadService::Shutdown() +{ + IOHIDManagerRef manager = (IOHIDManagerRef)mManager; + CFRunLoopStop(mMonitorRunLoop); + if (manager) { + IOHIDManagerClose(manager, 0); + CFRelease(manager); + mManager = nullptr; + } + mMonitorThread->Shutdown(); +} + +} // namespace + +namespace mozilla { +namespace dom { + +DarwinGamepadService* gService = nullptr; + +void StartGamepadMonitoring() +{ + if (gService) { + return; + } + + gService = new DarwinGamepadService(); + gService->Startup(); +} + +void StopGamepadMonitoring() +{ + if (!gService) { + return; + } + + gService->Shutdown(); + delete gService; + gService = nullptr; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/gamepad/moz.build b/dom/gamepad/moz.build index cee84f4290..1ed5830567 100644 --- a/dom/gamepad/moz.build +++ b/dom/gamepad/moz.build @@ -48,6 +48,10 @@ if CONFIG['MOZ_GAMEPAD']: SOURCES += [ 'fallback/FallbackGamepad.cpp' ] + elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'cocoa': + SOURCES += [ + 'cocoa/CocoaGamepad.cpp' + ] elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'windows': SOURCES += [ 'windows/WindowsGamepad.cpp' diff --git a/dom/geolocation/moz.build b/dom/geolocation/moz.build index 4a7d8b17be..441349e5f9 100644 --- a/dom/geolocation/moz.build +++ b/dom/geolocation/moz.build @@ -25,7 +25,11 @@ LOCAL_INCLUDES += [ '/dom/ipc', ] -if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/dom/system/mac', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': LOCAL_INCLUDES += [ '/dom/system/windows', ] diff --git a/dom/media/standalone/moz.build b/dom/media/standalone/moz.build index 5ec7e82c20..7ef15adaa9 100644 --- a/dom/media/standalone/moz.build +++ b/dom/media/standalone/moz.build @@ -13,6 +13,9 @@ SOURCES += [ '../VideoSegment.cpp', ] +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + UNIFIED_SOURCES += ['../systemservices/OSXRunLoopSingleton.cpp'] + LOCAL_INCLUDES += [ '/caps', '/dom/base', diff --git a/dom/media/systemservices/OSXRunLoopSingleton.cpp b/dom/media/systemservices/OSXRunLoopSingleton.cpp new file mode 100644 index 0000000000..6211d5c127 --- /dev/null +++ b/dom/media/systemservices/OSXRunLoopSingleton.cpp @@ -0,0 +1,44 @@ +/* -*- 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 "OSXRunLoopSingleton.h" +#include <mozilla/StaticMutex.h> + +#include <AudioUnit/AudioUnit.h> +#include <CoreAudio/AudioHardware.h> +#include <CoreAudio/HostTime.h> +#include <CoreFoundation/CoreFoundation.h> + +static bool gRunLoopSet = false; +static mozilla::StaticMutex gMutex; + +void mozilla_set_coreaudio_notification_runloop_if_needed() +{ + mozilla::StaticMutexAutoLock lock(gMutex); + if (gRunLoopSet) { + return; + } + + /* This is needed so that AudioUnit listeners get called on this thread, and + * not the main thread. If we don't do that, they are not called, or a crash + * occur, depending on the OSX version. */ + AudioObjectPropertyAddress runloop_address = { + kAudioHardwarePropertyRunLoop, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + CFRunLoopRef run_loop = nullptr; + + OSStatus r; + r = AudioObjectSetPropertyData(kAudioObjectSystemObject, + &runloop_address, + 0, NULL, sizeof(CFRunLoopRef), &run_loop); + if (r != noErr) { + NS_WARNING("Could not make global CoreAudio notifications use their own thread."); + } + + gRunLoopSet = true; +} diff --git a/dom/media/systemservices/OSXRunLoopSingleton.h b/dom/media/systemservices/OSXRunLoopSingleton.h new file mode 100644 index 0000000000..d06365e146 --- /dev/null +++ b/dom/media/systemservices/OSXRunLoopSingleton.h @@ -0,0 +1,25 @@ +/* -*- 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/. */ + +#ifndef OSXRUNLOOPSINGLETON_H_ +#define OSXRUNLOOPSINGLETON_H_ + +#include <mozilla/Types.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +/* This function tells CoreAudio to use its own thread for device change + * notifications, and can be called from any thread without external + * synchronization. */ +void MOZ_EXPORT +mozilla_set_coreaudio_notification_runloop_if_needed(); + +#if defined(__cplusplus) +} +#endif + +#endif // OSXRUNLOOPSINGLETON_H_ diff --git a/dom/media/systemservices/moz.build b/dom/media/systemservices/moz.build index 8fb5e54a19..402a6988c0 100644 --- a/dom/media/systemservices/moz.build +++ b/dom/media/systemservices/moz.build @@ -29,6 +29,10 @@ if CONFIG['OS_TARGET'] == 'WINNT': else: DEFINES['WEBRTC_POSIX'] = True +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += ['OSXRunLoopSingleton.cpp'] + EXPORTS += ['OSXRunLoopSingleton.h'] + if CONFIG['_MSC_VER']: DEFINES['__PRETTY_FUNCTION__'] = '__FUNCSIG__' diff --git a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp new file mode 100644 index 0000000000..ef69170003 --- /dev/null +++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; 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 "mozilla/ModuleUtils.h" +#include "nsIClassInfoImpl.h" + +#include "OSXSpeechSynthesizerService.h" + +using namespace mozilla::dom; + +#define OSXSPEECHSYNTHESIZERSERVICE_CID \ + {0x914e73b4, 0x6337, 0x4bef, {0x97, 0xf3, 0x4d, 0x06, 0x9e, 0x05, 0x3a, 0x12}} + +#define OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID "@mozilla.org/synthsystem;1" + +// Defines OSXSpeechSynthesizerServiceConstructor +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(OSXSpeechSynthesizerService, + OSXSpeechSynthesizerService::GetInstanceForService) + +// Defines kOSXSERVICE_CID +NS_DEFINE_NAMED_CID(OSXSPEECHSYNTHESIZERSERVICE_CID); + +static const mozilla::Module::CIDEntry kCIDs[] = { + { &kOSXSPEECHSYNTHESIZERSERVICE_CID, true, nullptr, OSXSpeechSynthesizerServiceConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kContracts[] = { + { OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID, &kOSXSPEECHSYNTHESIZERSERVICE_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kCategories[] = { + { "speech-synth-started", "OSX Speech Synth", OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID }, + { nullptr } +}; + +static void +UnloadOSXSpeechSynthesizerModule() +{ + OSXSpeechSynthesizerService::Shutdown(); +} + +static const mozilla::Module kModule = { + mozilla::Module::kVersion, + kCIDs, + kContracts, + kCategories, + nullptr, + nullptr, + UnloadOSXSpeechSynthesizerModule +}; + +NSMODULE_DEFN(osxsynth) = &kModule; diff --git a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h new file mode 100644 index 0000000000..ba04f0fecb --- /dev/null +++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_dom_OsxSpeechSynthesizerService_h +#define mozilla_dom_OsxSpeechSynthesizerService_h + +#include "nsISpeechService.h" +#include "nsIObserver.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { +namespace dom { + +class OSXSpeechSynthesizerService final : public nsISpeechService + , public nsIObserver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISPEECHSERVICE + NS_DECL_NSIOBSERVER + + bool Init(); + + static OSXSpeechSynthesizerService* GetInstance(); + static already_AddRefed<OSXSpeechSynthesizerService> GetInstanceForService(); + static void Shutdown(); + +private: + OSXSpeechSynthesizerService(); + virtual ~OSXSpeechSynthesizerService(); + + bool RegisterVoices(); + + bool mInitialized; + static mozilla::StaticRefPtr<OSXSpeechSynthesizerService> sSingleton; +}; + +} // namespace dom +} // namespace mozilla + +#endif diff --git a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm new file mode 100644 index 0000000000..ec752e00f0 --- /dev/null +++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm @@ -0,0 +1,498 @@ +/* -*- Mode: Objective-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 "nsISupports.h" +#include "nsServiceManagerUtils.h" +#include "nsObjCExceptions.h" +#include "nsCocoaUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/dom/nsSynthVoiceRegistry.h" +#include "mozilla/dom/nsSpeechTask.h" +#include "mozilla/Preferences.h" +#include "mozilla/Assertions.h" +#include "OSXSpeechSynthesizerService.h" + +#import <Cocoa/Cocoa.h> + +// We can escape the default delimiters ("[[" and "]]") by temporarily +// changing the delimiters just before they appear, and changing them back +// just after. +#define DLIM_ESCAPE_START "[[dlim (( ))]]" +#define DLIM_ESCAPE_END "((dlim [[ ]]))" + +using namespace mozilla; + +class SpeechTaskCallback final : public nsISpeechTaskCallback +{ +public: + SpeechTaskCallback(nsISpeechTask* aTask, + NSSpeechSynthesizer* aSynth, + const nsTArray<size_t>& aOffsets) + : mTask(aTask) + , mSpeechSynthesizer(aSynth) + , mOffsets(aOffsets) + { + mStartingTime = TimeStamp::Now(); + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SpeechTaskCallback, nsISpeechTaskCallback) + + NS_DECL_NSISPEECHTASKCALLBACK + + void OnWillSpeakWord(uint32_t aIndex); + void OnError(uint32_t aIndex); + void OnDidFinishSpeaking(); + +private: + virtual ~SpeechTaskCallback() + { + [mSpeechSynthesizer release]; + } + + float GetTimeDurationFromStart(); + + nsCOMPtr<nsISpeechTask> mTask; + NSSpeechSynthesizer* mSpeechSynthesizer; + TimeStamp mStartingTime; + uint32_t mCurrentIndex; + nsTArray<size_t> mOffsets; +}; + +NS_IMPL_CYCLE_COLLECTION(SpeechTaskCallback, mTask); + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechTaskCallback) + NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechTaskCallback) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechTaskCallback) + +NS_IMETHODIMP +SpeechTaskCallback::OnCancel() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + [mSpeechSynthesizer stopSpeaking]; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +SpeechTaskCallback::OnPause() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + [mSpeechSynthesizer pauseSpeakingAtBoundary:NSSpeechImmediateBoundary]; + if (!mTask) { + // When calling pause() on child porcess, it may not receive end event + // from chrome process yet. + return NS_ERROR_FAILURE; + } + mTask->DispatchPause(GetTimeDurationFromStart(), mCurrentIndex); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +SpeechTaskCallback::OnResume() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + [mSpeechSynthesizer continueSpeaking]; + if (!mTask) { + // When calling resume() on child porcess, it may not receive end event + // from chrome process yet. + return NS_ERROR_FAILURE; + } + mTask->DispatchResume(GetTimeDurationFromStart(), mCurrentIndex); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +SpeechTaskCallback::OnVolumeChanged(float aVolume) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + [mSpeechSynthesizer setObject:[NSNumber numberWithFloat:aVolume] + forProperty:NSSpeechVolumeProperty error:nil]; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +float +SpeechTaskCallback::GetTimeDurationFromStart() +{ + TimeDuration duration = TimeStamp::Now() - mStartingTime; + return duration.ToMilliseconds(); +} + +void +SpeechTaskCallback::OnWillSpeakWord(uint32_t aIndex) +{ + mCurrentIndex = aIndex < mOffsets.Length() ? mOffsets[aIndex] : mCurrentIndex; + if (!mTask) { + return; + } + mTask->DispatchBoundary(NS_LITERAL_STRING("word"), + GetTimeDurationFromStart(), mCurrentIndex); +} + +void +SpeechTaskCallback::OnError(uint32_t aIndex) +{ + if (!mTask) { + return; + } + mTask->DispatchError(GetTimeDurationFromStart(), aIndex); +} + +void +SpeechTaskCallback::OnDidFinishSpeaking() +{ + mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex); + // no longer needed + [mSpeechSynthesizer setDelegate:nil]; + mTask = nullptr; +} + +@interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate> +{ +@private + SpeechTaskCallback* mCallback; +} + + - (id)initWithCallback:(SpeechTaskCallback*)aCallback; +@end + +@implementation SpeechDelegate +- (id)initWithCallback:(SpeechTaskCallback*)aCallback +{ + [super init]; + mCallback = aCallback; + return self; +} + +- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender + willSpeakWord:(NSRange)aRange ofString:(NSString*)aString +{ + mCallback->OnWillSpeakWord(aRange.location); +} + +- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender + didFinishSpeaking:(BOOL)aFinishedSpeaking +{ + mCallback->OnDidFinishSpeaking(); +} + +- (void)speechSynthesizer:(NSSpeechSynthesizer*)aSender + didEncounterErrorAtIndex:(NSUInteger)aCharacterIndex + ofString:(NSString*)aString + message:(NSString*)aMessage +{ + mCallback->OnError(aCharacterIndex); +} +@end + +namespace mozilla { +namespace dom { + +struct OSXVoice +{ + OSXVoice() : mIsDefault(false) + { + } + + nsString mUri; + nsString mName; + nsString mLocale; + bool mIsDefault; +}; + +class RegisterVoicesRunnable final : public Runnable +{ +public: + RegisterVoicesRunnable(OSXSpeechSynthesizerService* aSpeechService, + nsTArray<OSXVoice>& aList) + : mSpeechService(aSpeechService) + , mVoices(aList) + { + } + + NS_IMETHOD Run() override; + +private: + ~RegisterVoicesRunnable() + { + } + + // This runnable always use sync mode. It is unnecesarry to reference object + OSXSpeechSynthesizerService* mSpeechService; + nsTArray<OSXVoice>& mVoices; +}; + +NS_IMETHODIMP +RegisterVoicesRunnable::Run() +{ + nsresult rv; + nsCOMPtr<nsISynthVoiceRegistry> registry = + do_GetService(NS_SYNTHVOICEREGISTRY_CONTRACTID, &rv); + if (!registry) { + return rv; + } + + for (OSXVoice voice : mVoices) { + rv = registry->AddVoice(mSpeechService, voice.mUri, voice.mName, voice.mLocale, true, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + if (voice.mIsDefault) { + registry->SetDefaultVoice(voice.mUri, true); + } + } + + registry->NotifyVoicesChanged(); + + return NS_OK; +} + +class EnumVoicesRunnable final : public Runnable +{ +public: + explicit EnumVoicesRunnable(OSXSpeechSynthesizerService* aSpeechService) + : mSpeechService(aSpeechService) + { + } + + NS_IMETHOD Run() override; + +private: + ~EnumVoicesRunnable() + { + } + + RefPtr<OSXSpeechSynthesizerService> mSpeechService; +}; + +NS_IMETHODIMP +EnumVoicesRunnable::Run() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + AutoTArray<OSXVoice, 64> list; + + NSArray* voices = [NSSpeechSynthesizer availableVoices]; + NSString* defaultVoice = [NSSpeechSynthesizer defaultVoice]; + + for (NSString* voice in voices) { + OSXVoice item; + + NSDictionary* attr = [NSSpeechSynthesizer attributesForVoice:voice]; + + nsAutoString identifier; + nsCocoaUtils::GetStringForNSString([attr objectForKey:NSVoiceIdentifier], + identifier); + + nsCocoaUtils::GetStringForNSString([attr objectForKey:NSVoiceName], item.mName); + + nsCocoaUtils::GetStringForNSString( + [attr objectForKey:NSVoiceLocaleIdentifier], item.mLocale); + item.mLocale.ReplaceChar('_', '-'); + + item.mUri.AssignLiteral("urn:moz-tts:osx:"); + item.mUri.Append(identifier); + + if ([voice isEqualToString:defaultVoice]) { + item.mIsDefault = true; + } + + list.AppendElement(item); + } + + RefPtr<RegisterVoicesRunnable> runnable = new RegisterVoicesRunnable(mSpeechService, list); + NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC); + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +StaticRefPtr<OSXSpeechSynthesizerService> OSXSpeechSynthesizerService::sSingleton; + +NS_INTERFACE_MAP_BEGIN(OSXSpeechSynthesizerService) + NS_INTERFACE_MAP_ENTRY(nsISpeechService) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechService) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(OSXSpeechSynthesizerService) +NS_IMPL_RELEASE(OSXSpeechSynthesizerService) + +OSXSpeechSynthesizerService::OSXSpeechSynthesizerService() + : mInitialized(false) +{ +} + +OSXSpeechSynthesizerService::~OSXSpeechSynthesizerService() +{ +} + +bool +OSXSpeechSynthesizerService::Init() +{ + if (Preferences::GetBool("media.webspeech.synth.test") || + !Preferences::GetBool("media.webspeech.synth.enabled")) { + // When test is enabled, we shouldn't add OS backend (Bug 1160844) + return false; + } + + nsCOMPtr<nsIThread> thread; + if (NS_FAILED(NS_NewNamedThread("SpeechWorker", getter_AddRefs(thread)))) { + return false; + } + + // Get all the voices and register in the SynthVoiceRegistry + nsCOMPtr<nsIRunnable> runnable = new EnumVoicesRunnable(this); + thread->Dispatch(runnable, NS_DISPATCH_NORMAL); + + mInitialized = true; + return true; +} + +NS_IMETHODIMP +OSXSpeechSynthesizerService::Speak(const nsAString& aText, + const nsAString& aUri, + float aVolume, + float aRate, + float aPitch, + nsISpeechTask* aTask) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + MOZ_ASSERT(StringBeginsWith(aUri, NS_LITERAL_STRING("urn:moz-tts:osx:")), + "OSXSpeechSynthesizerService doesn't allow this voice URI"); + + NSSpeechSynthesizer* synth = [[NSSpeechSynthesizer alloc] init]; + // strlen("urn:moz-tts:osx:") == 16 + NSString* identifier = nsCocoaUtils::ToNSString(Substring(aUri, 16)); + [synth setVoice:identifier]; + + // default rate is 180-220 + [synth setObject:[NSNumber numberWithInt:aRate * 200] + forProperty:NSSpeechRateProperty error:nil]; + // volume allows 0.0-1.0 + [synth setObject:[NSNumber numberWithFloat:aVolume] + forProperty:NSSpeechVolumeProperty error:nil]; + // Use default pitch value to calculate this + NSNumber* defaultPitch = + [synth objectForProperty:NSSpeechPitchBaseProperty error:nil]; + if (defaultPitch) { + int newPitch = [defaultPitch intValue] * (aPitch / 2 + 0.5); + [synth setObject:[NSNumber numberWithInt:newPitch] + forProperty:NSSpeechPitchBaseProperty error:nil]; + } + + nsAutoString escapedText; + // We need to map the the offsets from the given text to the escaped text. + // The index of the offsets array is the position in the escaped text, + // the element value is the position in the user-supplied text. + nsTArray<size_t> offsets; + offsets.SetCapacity(aText.Length()); + + // This loop looks for occurances of "[[" or "]]", escapes them, and + // populates the offsets array to supply a map to the original offsets. + for (size_t i = 0; i < aText.Length(); i++) { + if (aText.Length() > i + 1 && + ((aText[i] == ']' && aText[i+1] == ']') || + (aText[i] == '[' && aText[i+1] == '['))) { + escapedText.AppendLiteral(DLIM_ESCAPE_START); + offsets.AppendElements(strlen(DLIM_ESCAPE_START)); + escapedText.Append(aText[i]); + offsets.AppendElement(i); + escapedText.Append(aText[++i]); + offsets.AppendElement(i); + escapedText.AppendLiteral(DLIM_ESCAPE_END); + offsets.AppendElements(strlen(DLIM_ESCAPE_END)); + } else { + escapedText.Append(aText[i]); + offsets.AppendElement(i); + } + } + + RefPtr<SpeechTaskCallback> callback = new SpeechTaskCallback(aTask, synth, offsets); + nsresult rv = aTask->Setup(callback, 0, 0, 0); + NS_ENSURE_SUCCESS(rv, rv); + + SpeechDelegate* delegate = [[SpeechDelegate alloc] initWithCallback:callback]; + [synth setDelegate:delegate]; + [delegate release ]; + + NSString* text = nsCocoaUtils::ToNSString(escapedText); + BOOL success = [synth startSpeakingString:text]; + NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); + + aTask->DispatchStart(); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +OSXSpeechSynthesizerService::GetServiceType(SpeechServiceType* aServiceType) +{ + *aServiceType = nsISpeechService::SERVICETYPE_INDIRECT_AUDIO; + return NS_OK; +} + +NS_IMETHODIMP +OSXSpeechSynthesizerService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + return NS_OK; +} + +OSXSpeechSynthesizerService* +OSXSpeechSynthesizerService::GetInstance() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return nullptr; + } + + if (!sSingleton) { + RefPtr<OSXSpeechSynthesizerService> speechService = + new OSXSpeechSynthesizerService(); + if (speechService->Init()) { + sSingleton = speechService; + } + } + return sSingleton; +} + +already_AddRefed<OSXSpeechSynthesizerService> +OSXSpeechSynthesizerService::GetInstanceForService() +{ + RefPtr<OSXSpeechSynthesizerService> speechService = GetInstance(); + return speechService.forget(); +} + +void +OSXSpeechSynthesizerService::Shutdown() +{ + if (!sSingleton) { + return; + } + sSingleton = nullptr; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/media/webspeech/synth/cocoa/moz.build b/dom/media/webspeech/synth/cocoa/moz.build new file mode 100644 index 0000000000..6953a81e95 --- /dev/null +++ b/dom/media/webspeech/synth/cocoa/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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/. + +SOURCES += [ + 'OSXSpeechSynthesizerModule.cpp', + 'OSXSpeechSynthesizerService.mm' +] + +FINAL_LIBRARY = 'xul' diff --git a/dom/media/webspeech/synth/moz.build b/dom/media/webspeech/synth/moz.build index 9784686dfe..6603689261 100644 --- a/dom/media/webspeech/synth/moz.build +++ b/dom/media/webspeech/synth/moz.build @@ -32,6 +32,9 @@ SOURCES += [ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': DIRS += ['windows'] +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + DIRS += ['cocoa'] + if CONFIG['MOZ_SYNTH_SPEECHD']: DIRS += ['speechd'] diff --git a/dom/moz.build b/dom/moz.build index 17b0fed15b..ad278beb9c 100644 --- a/dom/moz.build +++ b/dom/moz.build @@ -111,6 +111,6 @@ TEST_DIRS += [ 'imptests', ] -if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'windows'): +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'cocoa', 'windows'): TEST_DIRS += ['plugins/test'] diff --git a/dom/plugins/base/moz.build b/dom/plugins/base/moz.build index 11649a8626..08f87d56a3 100644 --- a/dom/plugins/base/moz.build +++ b/dom/plugins/base/moz.build @@ -56,6 +56,11 @@ if CONFIG['OS_ARCH'] == 'WINNT': 'nsPluginNativeWindowWin.cpp', 'nsPluginsDirWin.cpp', ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'nsPluginNativeWindow.cpp', + 'nsPluginsDirDarwin.cpp', + ] else: SOURCES += [ 'nsPluginsDirUnix.cpp', @@ -77,6 +82,7 @@ LOCAL_INCLUDES += [ '/layout/xul', '/netwerk/base', '/widget', + '/widget/cocoa', '/xpcom/base', ] diff --git a/dom/plugins/base/nsPluginsDirDarwin.cpp b/dom/plugins/base/nsPluginsDirDarwin.cpp new file mode 100644 index 0000000000..0085eec0d6 --- /dev/null +++ b/dom/plugins/base/nsPluginsDirDarwin.cpp @@ -0,0 +1,572 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* + nsPluginsDirDarwin.cpp + + Mac OS X implementation of the nsPluginsDir/nsPluginsFile classes. + + by Patrick C. Beard. + */ + +#include "GeckoChildProcessHost.h" +#include "base/process_util.h" + +#include "prlink.h" +#include "prnetdb.h" +#include "nsXPCOM.h" + +#include "nsPluginsDir.h" +#include "nsNPAPIPlugin.h" +#include "nsPluginsDirUtils.h" + +#include "nsILocalFileMac.h" +#include "mozilla/UniquePtr.h" + +#include "nsCocoaFeatures.h" + +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +#include <Carbon/Carbon.h> +#include <CoreServices/CoreServices.h> +#include <mach-o/loader.h> +#include <mach-o/fat.h> + +typedef NS_NPAPIPLUGIN_CALLBACK(const char *, NP_GETMIMEDESCRIPTION) (); +typedef NS_NPAPIPLUGIN_CALLBACK(OSErr, BP_GETSUPPORTEDMIMETYPES) (BPSupportedMIMETypes *mimeInfo, UInt32 flags); + +/* +** Returns a CFBundleRef if the path refers to a Mac OS X bundle directory. +** The caller is responsible for calling CFRelease() to deallocate. +*/ +static CFBundleRef getPluginBundle(const char* path) +{ + CFBundleRef bundle = nullptr; + CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, path, + kCFStringEncodingUTF8); + if (pathRef) { + CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef, + kCFURLPOSIXPathStyle, + true); + if (bundleURL) { + bundle = ::CFBundleCreate(nullptr, bundleURL); + ::CFRelease(bundleURL); + } + ::CFRelease(pathRef); + } + return bundle; +} + +static nsresult toCFURLRef(nsIFile* file, CFURLRef& outURL) +{ + nsCOMPtr<nsILocalFileMac> lfm = do_QueryInterface(file); + if (!lfm) + return NS_ERROR_FAILURE; + CFURLRef url; + nsresult rv = lfm->GetCFURL(&url); + if (NS_SUCCEEDED(rv)) + outURL = url; + + return rv; +} + +bool nsPluginsDir::IsPluginFile(nsIFile* file) +{ + nsCString fileName; + file->GetNativeLeafName(fileName); + /* + * Don't load the VDP fake plugin, to avoid tripping a bad bug in OS X + * 10.5.3 (see bug 436575). + */ + if (!strcmp(fileName.get(), "VerifiedDownloadPlugin.plugin")) { + NS_WARNING("Preventing load of VerifiedDownloadPlugin.plugin (see bug 436575)"); + return false; + } + return true; +} + +// Caller is responsible for freeing returned buffer. +static char* CFStringRefToUTF8Buffer(CFStringRef cfString) +{ + const char* buffer = ::CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8); + if (buffer) { + return PL_strdup(buffer); + } + + int bufferLength = + ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(cfString), + kCFStringEncodingUTF8) + 1; + char* newBuffer = static_cast<char*>(moz_xmalloc(bufferLength)); + if (!newBuffer) { + return nullptr; + } + + if (!::CFStringGetCString(cfString, newBuffer, bufferLength, + kCFStringEncodingUTF8)) { + free(newBuffer); + return nullptr; + } + + newBuffer = static_cast<char*>(moz_xrealloc(newBuffer, + strlen(newBuffer) + 1)); + return newBuffer; +} + +class AutoCFTypeObject { +public: + explicit AutoCFTypeObject(CFTypeRef aObject) + { + mObject = aObject; + } + ~AutoCFTypeObject() + { + ::CFRelease(mObject); + } +private: + CFTypeRef mObject; +}; + +static Boolean MimeTypeEnabled(CFDictionaryRef mimeDict) { + if (!mimeDict) { + return true; + } + + CFTypeRef value; + if (::CFDictionaryGetValueIfPresent(mimeDict, CFSTR("WebPluginTypeEnabled"), &value)) { + if (value && ::CFGetTypeID(value) == ::CFBooleanGetTypeID()) { + return ::CFBooleanGetValue(static_cast<CFBooleanRef>(value)); + } + } + return true; +} + +static CFDictionaryRef ParsePlistForMIMETypesFilename(CFBundleRef bundle) +{ + CFTypeRef mimeFileName = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename")); + if (!mimeFileName || ::CFGetTypeID(mimeFileName) != ::CFStringGetTypeID()) { + return nullptr; + } + + FSRef homeDir; + if (::FSFindFolder(kUserDomain, kCurrentUserFolderType, kDontCreateFolder, &homeDir) != noErr) { + return nullptr; + } + + CFURLRef userDirURL = ::CFURLCreateFromFSRef(kCFAllocatorDefault, &homeDir); + if (!userDirURL) { + return nullptr; + } + + AutoCFTypeObject userDirURLAutorelease(userDirURL); + CFStringRef mimeFilePath = ::CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("Library/Preferences/%@"), static_cast<CFStringRef>(mimeFileName)); + if (!mimeFilePath) { + return nullptr; + } + + AutoCFTypeObject mimeFilePathAutorelease(mimeFilePath); + CFURLRef mimeFileURL = ::CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorDefault, mimeFilePath, kCFURLPOSIXPathStyle, false, userDirURL); + if (!mimeFileURL) { + return nullptr; + } + + AutoCFTypeObject mimeFileURLAutorelease(mimeFileURL); + SInt32 errorCode = 0; + CFDataRef mimeFileData = nullptr; + Boolean result = ::CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, mimeFileURL, &mimeFileData, nullptr, nullptr, &errorCode); + if (!result) { + return nullptr; + } + + AutoCFTypeObject mimeFileDataAutorelease(mimeFileData); + if (errorCode != 0) { + return nullptr; + } + + CFPropertyListRef propertyList = ::CFPropertyListCreateFromXMLData(kCFAllocatorDefault, mimeFileData, kCFPropertyListImmutable, nullptr); + if (!propertyList) { + return nullptr; + } + + AutoCFTypeObject propertyListAutorelease(propertyList); + if (::CFGetTypeID(propertyList) != ::CFDictionaryGetTypeID()) { + return nullptr; + } + + CFTypeRef mimeTypes = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyList), CFSTR("WebPluginMIMETypes")); + if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) { + return nullptr; + } + + return static_cast<CFDictionaryRef>(::CFRetain(mimeTypes)); +} + +static void ParsePlistPluginInfo(nsPluginInfo& info, CFBundleRef bundle) +{ + CFDictionaryRef mimeDict = ParsePlistForMIMETypesFilename(bundle); + + if (!mimeDict) { + CFTypeRef mimeTypes = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes")); + if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) + return; + mimeDict = static_cast<CFDictionaryRef>(::CFRetain(mimeTypes)); + } + + AutoCFTypeObject mimeDictAutorelease(mimeDict); + int mimeDictKeyCount = ::CFDictionaryGetCount(mimeDict); + + // Allocate memory for mime data + int mimeDataArraySize = mimeDictKeyCount * sizeof(char*); + info.fMimeTypeArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + if (!info.fMimeTypeArray) + return; + memset(info.fMimeTypeArray, 0, mimeDataArraySize); + info.fExtensionArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + if (!info.fExtensionArray) + return; + memset(info.fExtensionArray, 0, mimeDataArraySize); + info.fMimeDescriptionArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + if (!info.fMimeDescriptionArray) + return; + memset(info.fMimeDescriptionArray, 0, mimeDataArraySize); + + // Allocate memory for mime dictionary keys and values + mozilla::UniquePtr<CFTypeRef[]> keys(new CFTypeRef[mimeDictKeyCount]); + if (!keys) + return; + mozilla::UniquePtr<CFTypeRef[]> values(new CFTypeRef[mimeDictKeyCount]); + if (!values) + return; + + info.fVariantCount = 0; + + ::CFDictionaryGetKeysAndValues(mimeDict, keys.get(), values.get()); + for (int i = 0; i < mimeDictKeyCount; i++) { + CFTypeRef mimeString = keys[i]; + if (!mimeString || ::CFGetTypeID(mimeString) != ::CFStringGetTypeID()) { + continue; + } + CFTypeRef mimeDict = values[i]; + if (mimeDict && ::CFGetTypeID(mimeDict) == ::CFDictionaryGetTypeID()) { + if (!MimeTypeEnabled(static_cast<CFDictionaryRef>(mimeDict))) { + continue; + } + info.fMimeTypeArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(mimeString)); + if (!info.fMimeTypeArray[info.fVariantCount]) { + continue; + } + CFTypeRef extensions = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginExtensions")); + if (extensions && ::CFGetTypeID(extensions) == ::CFArrayGetTypeID()) { + int extensionCount = ::CFArrayGetCount(static_cast<CFArrayRef>(extensions)); + CFMutableStringRef extensionList = ::CFStringCreateMutable(kCFAllocatorDefault, 0); + for (int j = 0; j < extensionCount; j++) { + CFTypeRef extension = ::CFArrayGetValueAtIndex(static_cast<CFArrayRef>(extensions), j); + if (extension && ::CFGetTypeID(extension) == ::CFStringGetTypeID()) { + if (j > 0) + ::CFStringAppend(extensionList, CFSTR(",")); + ::CFStringAppend(static_cast<CFMutableStringRef>(extensionList), static_cast<CFStringRef>(extension)); + } + } + info.fExtensionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(extensionList)); + ::CFRelease(extensionList); + } + CFTypeRef description = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginTypeDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fMimeDescriptionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description)); + } + info.fVariantCount++; + } +} + +nsPluginFile::nsPluginFile(nsIFile *spec) + : mPlugin(spec) +{ +} + +nsPluginFile::~nsPluginFile() {} + +nsresult nsPluginFile::LoadPlugin(PRLibrary **outLibrary) +{ + if (!mPlugin) + return NS_ERROR_NULL_POINTER; + + // 64-bit NSPR does not (yet) support bundles. So in 64-bit builds we need + // (for now) to load the bundle's executable. However this can cause + // problems: CFBundleCreate() doesn't run the bundle's executable's + // initialization code, while NSAddImage() and dlopen() do run it. So using + // NSPR's dyld loading mechanisms here (NSAddImage() or dlopen()) can cause + // a bundle's initialization code to run earlier than expected, and lead to + // crashes. See bug 577967. +#ifdef __LP64__ + char executablePath[PATH_MAX]; + executablePath[0] = '\0'; + nsAutoCString bundlePath; + mPlugin->GetNativePath(bundlePath); + CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, bundlePath.get(), + kCFStringEncodingUTF8); + if (pathRef) { + CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef, + kCFURLPOSIXPathStyle, + true); + if (bundleURL) { + CFBundleRef bundle = ::CFBundleCreate(nullptr, bundleURL); + if (bundle) { + CFURLRef executableURL = ::CFBundleCopyExecutableURL(bundle); + if (executableURL) { + if (!::CFURLGetFileSystemRepresentation(executableURL, true, (UInt8*)&executablePath, PATH_MAX)) + executablePath[0] = '\0'; + ::CFRelease(executableURL); + } + ::CFRelease(bundle); + } + ::CFRelease(bundleURL); + } + ::CFRelease(pathRef); + } +#else + nsAutoCString bundlePath; + mPlugin->GetNativePath(bundlePath); + const char *executablePath = bundlePath.get(); +#endif + + *outLibrary = PR_LoadLibrary(executablePath); + pLibrary = *outLibrary; + if (!pLibrary) { + return NS_ERROR_FAILURE; + } +#ifdef DEBUG + printf("[loaded plugin %s]\n", bundlePath.get()); +#endif + return NS_OK; +} + +static char* p2cstrdup(StringPtr pstr) +{ + int len = pstr[0]; + char* cstr = static_cast<char*>(moz_xmalloc(len + 1)); + if (cstr) { + memmove(cstr, pstr + 1, len); + cstr[len] = '\0'; + } + return cstr; +} + +static char* GetNextPluginStringFromHandle(Handle h, short *index) +{ + char *ret = p2cstrdup((unsigned char*)(*h + *index)); + *index += (ret ? strlen(ret) : 0) + 1; + return ret; +} + +static bool IsCompatibleArch(nsIFile *file) +{ + CFURLRef pluginURL = nullptr; + if (NS_FAILED(toCFURLRef(file, pluginURL))) + return false; + + bool isPluginFile = false; + + CFBundleRef pluginBundle = ::CFBundleCreate(kCFAllocatorDefault, pluginURL); + if (pluginBundle) { + UInt32 packageType, packageCreator; + ::CFBundleGetPackageInfo(pluginBundle, &packageType, &packageCreator); + if (packageType == 'BRPL' || packageType == 'IEPL' || packageType == 'NSPL') { + // Get path to plugin as a C string. + char executablePath[PATH_MAX]; + executablePath[0] = '\0'; + if (!::CFURLGetFileSystemRepresentation(pluginURL, true, (UInt8*)&executablePath, PATH_MAX)) { + executablePath[0] = '\0'; + } + + uint32_t pluginLibArchitectures; + nsresult rv = mozilla::ipc::GeckoChildProcessHost::GetArchitecturesForBinary(executablePath, &pluginLibArchitectures); + if (NS_FAILED(rv)) { + return false; + } + + uint32_t supportedArchitectures = +#ifdef __LP64__ + mozilla::ipc::GeckoChildProcessHost::GetSupportedArchitecturesForProcessType(GeckoProcessType_Plugin); +#else + base::GetCurrentProcessArchitecture(); +#endif + + // Consider the plugin architecture valid if there is any overlap in the masks. + isPluginFile = !!(supportedArchitectures & pluginLibArchitectures); + } + ::CFRelease(pluginBundle); + } + + ::CFRelease(pluginURL); + return isPluginFile; +} + +/** + * Obtains all of the information currently available for this plugin. + */ +nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, PRLibrary **outLibrary) +{ + *outLibrary = nullptr; + + nsresult rv = NS_OK; + + if (!IsCompatibleArch(mPlugin)) { + return NS_ERROR_FAILURE; + } + + // clear out the info, except for the first field. + memset(&info, 0, sizeof(info)); + + // Try to get a bundle reference. + nsAutoCString path; + if (NS_FAILED(rv = mPlugin->GetNativePath(path))) + return rv; + CFBundleRef bundle = getPluginBundle(path.get()); + + // fill in full path + info.fFullPath = PL_strdup(path.get()); + + // fill in file name + nsAutoCString fileName; + if (NS_FAILED(rv = mPlugin->GetNativeLeafName(fileName))) + return rv; + info.fFileName = PL_strdup(fileName.get()); + + // Get fName + if (bundle) { + CFTypeRef name = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName")); + if (name && ::CFGetTypeID(name) == ::CFStringGetTypeID()) + info.fName = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(name)); + } + + // Get fDescription + if (bundle) { + CFTypeRef description = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fDescription = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description)); + } + + // Get fVersion + if (bundle) { + // Look for the release version first + CFTypeRef version = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString")); + if (!version) // try the build version + version = ::CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey); + if (version && ::CFGetTypeID(version) == ::CFStringGetTypeID()) + info.fVersion = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(version)); + } + + // The last thing we need to do is get MIME data + // fVariantCount, fMimeTypeArray, fExtensionArray, fMimeDescriptionArray + + // First look for data in a bundle plist + if (bundle) { + ParsePlistPluginInfo(info, bundle); + ::CFRelease(bundle); + if (info.fVariantCount > 0) + return NS_OK; + } + + // Don't load "fbplugin" or any plugins whose name starts with "fbplugin_" + // (Facebook plugins) if we're running on OS X 10.10 (Yosemite) or later. + // A "fbplugin" file crashes on load, in the call to LoadPlugin() below. + // See bug 1086977. + if (nsCocoaFeatures::OnYosemiteOrLater()) { + if (fileName.EqualsLiteral("fbplugin") || + StringBeginsWith(fileName, NS_LITERAL_CSTRING("fbplugin_"))) { + nsAutoCString msg; + msg.AppendPrintf("Preventing load of %s (see bug 1086977)", + fileName.get()); + NS_WARNING(msg.get()); + return NS_ERROR_FAILURE; + } + } + + // It's possible that our plugin has 2 entry points that'll give us mime type + // info. Quicktime does this to get around the need of having admin rights to + // change mime info in the resource fork. We need to use this info instead of + // the resource. See bug 113464. + + // Sadly we have to load the library for this to work. + rv = LoadPlugin(outLibrary); + if (NS_FAILED(rv)) + return rv; + + // Try to get data from NP_GetMIMEDescription + if (pLibrary) { + NP_GETMIMEDESCRIPTION pfnGetMimeDesc = (NP_GETMIMEDESCRIPTION)PR_FindFunctionSymbol(pLibrary, NP_GETMIMEDESCRIPTION_NAME); + if (pfnGetMimeDesc) + ParsePluginMimeDescription(pfnGetMimeDesc(), info); + if (info.fVariantCount) + return NS_OK; + } + + // We'll fill this in using BP_GetSupportedMIMETypes and/or resource fork data + BPSupportedMIMETypes mi = {kBPSupportedMIMETypesStructVers_1, nullptr, nullptr}; + + // Try to get data from BP_GetSupportedMIMETypes + if (pLibrary) { + BP_GETSUPPORTEDMIMETYPES pfnMime = (BP_GETSUPPORTEDMIMETYPES)PR_FindFunctionSymbol(pLibrary, "BP_GetSupportedMIMETypes"); + if (pfnMime && noErr == pfnMime(&mi, 0) && mi.typeStrings) { + info.fVariantCount = (**(short**)mi.typeStrings) / 2; + ::HLock(mi.typeStrings); + if (mi.infoStrings) // it's possible some plugins have infoStrings missing + ::HLock(mi.infoStrings); + } + } + + // Fill in the info struct based on the data in the BPSupportedMIMETypes struct + int variantCount = info.fVariantCount; + info.fMimeTypeArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (!info.fMimeTypeArray) + return NS_ERROR_OUT_OF_MEMORY; + info.fExtensionArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (!info.fExtensionArray) + return NS_ERROR_OUT_OF_MEMORY; + if (mi.infoStrings) { + info.fMimeDescriptionArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (!info.fMimeDescriptionArray) + return NS_ERROR_OUT_OF_MEMORY; + } + short mimeIndex = 2; + short descriptionIndex = 2; + for (int i = 0; i < variantCount; i++) { + info.fMimeTypeArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex); + info.fExtensionArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex); + if (mi.infoStrings) + info.fMimeDescriptionArray[i] = GetNextPluginStringFromHandle(mi.infoStrings, &descriptionIndex); + } + + ::HUnlock(mi.typeStrings); + ::DisposeHandle(mi.typeStrings); + if (mi.infoStrings) { + ::HUnlock(mi.infoStrings); + ::DisposeHandle(mi.infoStrings); + } + + return NS_OK; +} + +nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info) +{ + free(info.fName); + free(info.fDescription); + int variantCount = info.fVariantCount; + for (int i = 0; i < variantCount; i++) { + free(info.fMimeTypeArray[i]); + free(info.fExtensionArray[i]); + free(info.fMimeDescriptionArray[i]); + } + free(info.fMimeTypeArray); + free(info.fMimeDescriptionArray); + free(info.fExtensionArray); + free(info.fFileName); + free(info.fFullPath); + free(info.fVersion); + + return NS_OK; +} diff --git a/dom/system/mac/CoreLocationLocationProvider.h b/dom/system/mac/CoreLocationLocationProvider.h new file mode 100644 index 0000000000..979bc916d8 --- /dev/null +++ b/dom/system/mac/CoreLocationLocationProvider.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; 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 "nsCOMPtr.h" +#include "nsIGeolocationProvider.h" + + +/* + * The CoreLocationObjects class contains the CoreLocation objects + * we'll need. + * + * Declaring them directly in CoreLocationLocationProvider + * would require Objective-C++ syntax, which would contaminate all + * files that include this header and require them to be Objective-C++ + * as well. + * + * The solution then is to forward-declare CoreLocationObjects here and + * hold a pointer to it in CoreLocationLocationProvider, and only actually + * define it in CoreLocationLocationProvider.mm, thus making it safe + * for nsGeolocation.cpp, which is C++-only, to include this header. + */ +class CoreLocationObjects; +class MLSFallback; + +class CoreLocationLocationProvider + : public nsIGeolocationProvider +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIGEOLOCATIONPROVIDER + + CoreLocationLocationProvider(); + void NotifyError(uint16_t aErrorCode); + void Update(nsIDOMGeoPosition* aSomewhere); + void CreateMLSFallbackProvider(); + void CancelMLSFallbackProvider(); + +private: + virtual ~CoreLocationLocationProvider(); + + CoreLocationObjects* mCLObjects; + nsCOMPtr<nsIGeolocationUpdate> mCallback; + RefPtr<MLSFallback> mMLSFallbackProvider; + + class MLSUpdate : public nsIGeolocationUpdate + { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIGEOLOCATIONUPDATE + + explicit MLSUpdate(CoreLocationLocationProvider& parentProvider); + + private: + CoreLocationLocationProvider& mParentLocationProvider; + virtual ~MLSUpdate(); + }; +}; diff --git a/dom/system/mac/CoreLocationLocationProvider.mm b/dom/system/mac/CoreLocationLocationProvider.mm new file mode 100644 index 0000000000..7a3feba97b --- /dev/null +++ b/dom/system/mac/CoreLocationLocationProvider.mm @@ -0,0 +1,268 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsGeoPosition.h" +#include "nsIConsoleService.h" +#include "nsServiceManagerUtils.h" +#include "nsIDOMGeoPositionError.h" +#include "CoreLocationLocationProvider.h" +#include "nsCocoaFeatures.h" +#include "prtime.h" +#include "MLSFallback.h" + +#include <CoreLocation/CLError.h> +#include <CoreLocation/CLLocation.h> +#include <CoreLocation/CLLocationManager.h> +#include <CoreLocation/CLLocationManagerDelegate.h> + +#include <objc/objc.h> +#include <objc/objc-runtime.h> + +#include "nsObjCExceptions.h" + +using namespace mozilla; + +static const CLLocationAccuracy kHIGH_ACCURACY = kCLLocationAccuracyBest; +static const CLLocationAccuracy kDEFAULT_ACCURACY = kCLLocationAccuracyNearestTenMeters; + +@interface LocationDelegate : NSObject <CLLocationManagerDelegate> +{ + CoreLocationLocationProvider* mProvider; +} + +- (id)init:(CoreLocationLocationProvider*)aProvider; +- (void)locationManager:(CLLocationManager*)aManager + didFailWithError:(NSError *)aError; +- (void)locationManager:(CLLocationManager*)aManager didUpdateLocations:(NSArray*)locations; + +@end + +@implementation LocationDelegate +- (id) init:(CoreLocationLocationProvider*) aProvider +{ + if ((self = [super init])) { + mProvider = aProvider; + } + + return self; +} + +- (void)locationManager:(CLLocationManager*)aManager + didFailWithError:(NSError *)aError +{ + nsCOMPtr<nsIConsoleService> console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + + NS_ENSURE_TRUE_VOID(console); + + NSString* message = + [@"Failed to acquire position: " stringByAppendingString: [aError localizedDescription]]; + + console->LogStringMessage(NS_ConvertUTF8toUTF16([message UTF8String]).get()); + + if ([aError code] == kCLErrorDenied) { + mProvider->NotifyError(nsIDOMGeoPositionError::PERMISSION_DENIED); + return; + } + + // The CL provider does not fallback to GeoIP, so use NetworkGeolocationProvider for this. + // The concept here is: on error, hand off geolocation to MLS, which will then report + // back a location or error. We can't call this with no delay however, as this method + // is called with an error code of 0 in both failed geolocation cases, and also when + // geolocation is not immediately available. + // The 2 sec delay is arbitrarily large enough that CL has a reasonable head start and + // if it is likely to succeed, it should complete before the MLS provider. + // Take note that in locationManager:didUpdateLocations: the handoff to MLS is stopped. + mProvider->CreateMLSFallbackProvider(); +} + +- (void)locationManager:(CLLocationManager*)aManager didUpdateLocations:(NSArray*)aLocations +{ + if (aLocations.count < 1) { + return; + } + + mProvider->CancelMLSFallbackProvider(); + + CLLocation* location = [aLocations objectAtIndex:0]; + + nsCOMPtr<nsIDOMGeoPosition> geoPosition = + new nsGeoPosition(location.coordinate.latitude, + location.coordinate.longitude, + location.altitude, + location.horizontalAccuracy, + location.verticalAccuracy, + location.course, + location.speed, + PR_Now() / PR_USEC_PER_MSEC); + + mProvider->Update(geoPosition); +} +@end + +NS_IMPL_ISUPPORTS(CoreLocationLocationProvider::MLSUpdate, nsIGeolocationUpdate); + +CoreLocationLocationProvider::MLSUpdate::MLSUpdate(CoreLocationLocationProvider& parentProvider) + : mParentLocationProvider(parentProvider) +{ +} + +CoreLocationLocationProvider::MLSUpdate::~MLSUpdate() +{ +} + +NS_IMETHODIMP +CoreLocationLocationProvider::MLSUpdate::Update(nsIDOMGeoPosition *position) +{ + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + position->GetCoords(getter_AddRefs(coords)); + if (!coords) { + return NS_ERROR_FAILURE; + } + mParentLocationProvider.Update(position); + return NS_OK; +} +NS_IMETHODIMP +CoreLocationLocationProvider::MLSUpdate::NotifyError(uint16_t error) +{ + mParentLocationProvider.NotifyError(error); + return NS_OK; +} +class CoreLocationObjects { +public: + nsresult Init(CoreLocationLocationProvider* aProvider) { + mLocationManager = [[CLLocationManager alloc] init]; + NS_ENSURE_TRUE(mLocationManager, NS_ERROR_NOT_AVAILABLE); + + mLocationDelegate = [[LocationDelegate alloc] init:aProvider]; + NS_ENSURE_TRUE(mLocationDelegate, NS_ERROR_NOT_AVAILABLE); + + mLocationManager.desiredAccuracy = kDEFAULT_ACCURACY; + mLocationManager.delegate = mLocationDelegate; + + return NS_OK; + } + + ~CoreLocationObjects() { + if (mLocationManager) { + [mLocationManager release]; + } + + if (mLocationDelegate) { + [mLocationDelegate release]; + } + } + + LocationDelegate* mLocationDelegate; + CLLocationManager* mLocationManager; +}; + +NS_IMPL_ISUPPORTS(CoreLocationLocationProvider, nsIGeolocationProvider) + +CoreLocationLocationProvider::CoreLocationLocationProvider() + : mCLObjects(nullptr), mMLSFallbackProvider(nullptr) +{ +} + +CoreLocationLocationProvider::~CoreLocationLocationProvider() +{ +} + +NS_IMETHODIMP +CoreLocationLocationProvider::Startup() +{ + if (!mCLObjects) { + nsAutoPtr<CoreLocationObjects> clObjs(new CoreLocationObjects()); + + nsresult rv = clObjs->Init(this); + NS_ENSURE_SUCCESS(rv, rv); + + mCLObjects = clObjs.forget(); + } + + // Must be stopped before starting or response (success or failure) is not guaranteed + [mCLObjects->mLocationManager stopUpdatingLocation]; + [mCLObjects->mLocationManager startUpdatingLocation]; + return NS_OK; +} + +NS_IMETHODIMP +CoreLocationLocationProvider::Watch(nsIGeolocationUpdate* aCallback) +{ + if (mCallback) { + return NS_OK; + } + + mCallback = aCallback; + return NS_OK; +} + +NS_IMETHODIMP +CoreLocationLocationProvider::Shutdown() +{ + NS_ENSURE_STATE(mCLObjects); + + [mCLObjects->mLocationManager stopUpdatingLocation]; + + delete mCLObjects; + mCLObjects = nullptr; + + if (mMLSFallbackProvider) { + mMLSFallbackProvider->Shutdown(); + mMLSFallbackProvider = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +CoreLocationLocationProvider::SetHighAccuracy(bool aEnable) +{ + NS_ENSURE_STATE(mCLObjects); + + mCLObjects->mLocationManager.desiredAccuracy = + (aEnable ? kHIGH_ACCURACY : kDEFAULT_ACCURACY); + + return NS_OK; +} + +void +CoreLocationLocationProvider::Update(nsIDOMGeoPosition* aSomewhere) +{ + if (aSomewhere && mCallback) { + mCallback->Update(aSomewhere); + } +} + +void +CoreLocationLocationProvider::NotifyError(uint16_t aErrorCode) +{ + mCallback->NotifyError(aErrorCode); +} + +void +CoreLocationLocationProvider::CreateMLSFallbackProvider() +{ + if (mMLSFallbackProvider) { + return; + } + + mMLSFallbackProvider = new MLSFallback(); + mMLSFallbackProvider->Startup(new MLSUpdate(*this)); +} + +void +CoreLocationLocationProvider::CancelMLSFallbackProvider() +{ + if (!mMLSFallbackProvider) { + return; + } + + mMLSFallbackProvider->Shutdown(); + mMLSFallbackProvider = nullptr; +} diff --git a/dom/system/mac/moz.build b/dom/system/mac/moz.build new file mode 100644 index 0000000000..08b7c2151e --- /dev/null +++ b/dom/system/mac/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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/. + +SOURCES += ['CoreLocationLocationProvider.mm'] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/dom/geolocation', +] + diff --git a/dom/system/moz.build b/dom/system/moz.build index 31097d2481..7e42761e5e 100644 --- a/dom/system/moz.build +++ b/dom/system/moz.build @@ -7,6 +7,8 @@ toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] if toolkit == 'windows': DIRS += ['windows'] +elif toolkit == 'cocoa': + DIRS += ['mac'] elif toolkit in ('gtk2', 'gtk3'): DIRS += ['linux'] diff --git a/dom/xbl/builtin/mac/jar.mn b/dom/xbl/builtin/mac/jar.mn new file mode 100644 index 0000000000..9f05c2dd6c --- /dev/null +++ b/dom/xbl/builtin/mac/jar.mn @@ -0,0 +1,6 @@ +# 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/. + +toolkit.jar: +* content/global/platformHTMLBindings.xml (platformHTMLBindings.xml) diff --git a/dom/xbl/builtin/mac/moz.build b/dom/xbl/builtin/mac/moz.build new file mode 100644 index 0000000000..635fa39c99 --- /dev/null +++ b/dom/xbl/builtin/mac/moz.build @@ -0,0 +1,6 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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/. + +JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file diff --git a/dom/xbl/builtin/mac/platformHTMLBindings.xml b/dom/xbl/builtin/mac/platformHTMLBindings.xml new file mode 100644 index 0000000000..b705923999 --- /dev/null +++ b/dom/xbl/builtin/mac/platformHTMLBindings.xml @@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<!-- 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/. --> + + +<bindings id="htmlBindings" + xmlns="http://www.mozilla.org/xbl" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <binding id="inputFields" bindToUntrustedContent="true"> + <handlers> + <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/> + <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/> + <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/> + <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/> + <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/> + <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/> + </handlers> + </binding> + + <binding id="textAreas" bindToUntrustedContent="true"> + <handlers> + <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/> + <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/> + <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/> + <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/> + <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/> + <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/> + </handlers> + </binding> + + <binding id="browser"> + <handlers> +#include ../browser-base.inc + <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_scrollPageUp"/> + <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_scrollPageDown"/> + <handler event="keypress" keycode="VK_HOME" command="cmd_scrollTop" /> + <handler event="keypress" keycode="VK_END" command="cmd_scrollBottom" /> + + <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_moveLeft2" /> + <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_moveRight2" /> + <handler event="keypress" keycode="VK_LEFT" modifiers="alt,shift" command="cmd_selectLeft2" /> + <handler event="keypress" keycode="VK_RIGHT" modifiers="alt,shift" command="cmd_selectRight2" /> + <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft" /> + <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight" /> + <handler event="keypress" keycode="VK_UP" modifiers="alt,shift" command="cmd_selectUp2" /> + <handler event="keypress" keycode="VK_DOWN" modifiers="alt,shift" command="cmd_selectDown2" /> + <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp" /> + <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown" /> + <handler event="keypress" keycode="VK_UP" modifiers="accel" command="cmd_moveUp2"/> + <handler event="keypress" keycode="VK_DOWN" modifiers="accel" command="cmd_moveDown2"/> + </handlers> + </binding> + + <binding id="editor"> + <handlers> + <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" /> + <handler event="keypress" key=" " command="cmd_scrollPageDown" /> + + <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/> + <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" /> + <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/> + <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/> + <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/> + <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,shift"/> + <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/> + <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,alt,shift"/> + </handlers> + </binding> + +</bindings> diff --git a/dom/xbl/builtin/moz.build b/dom/xbl/builtin/moz.build index b6c41678c4..27d3e6c146 100644 --- a/dom/xbl/builtin/moz.build +++ b/dom/xbl/builtin/moz.build @@ -5,6 +5,8 @@ if CONFIG['OS_ARCH'] == 'WINNT': DIRS += ['win'] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + DIRS += ['mac'] elif CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'): DIRS += ['unix'] else: |