summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Smith <brian@dbsoft.org>2022-04-26 09:57:40 -0500
committerBrian Smith <brian@dbsoft.org>2022-04-26 10:19:03 -0500
commit93407295fc1e294855b7943cb00c00d2d4debc02 (patch)
treec485b918766c8618123f068740e72463f8acec63
parentdb8d2ef67236c020764c5c01546dacd2c362ddd5 (diff)
downloaduxp-93407295fc1e294855b7943cb00c00d2d4debc02.tar.gz
Issue #1829 - Revert “Issue #1751 -- Remove cocoa support code from /dom”
This reverts commit ca35efb84ebae522f9ab7803d8e017f721e03207.
-rw-r--r--dom/gamepad/cocoa/CocoaGamepad.cpp590
-rw-r--r--dom/gamepad/moz.build4
-rw-r--r--dom/geolocation/moz.build6
-rw-r--r--dom/media/standalone/moz.build3
-rw-r--r--dom/media/systemservices/OSXRunLoopSingleton.cpp44
-rw-r--r--dom/media/systemservices/OSXRunLoopSingleton.h25
-rw-r--r--dom/media/systemservices/moz.build4
-rw-r--r--dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp56
-rw-r--r--dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h43
-rw-r--r--dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm498
-rw-r--r--dom/media/webspeech/synth/cocoa/moz.build11
-rw-r--r--dom/media/webspeech/synth/moz.build3
-rw-r--r--dom/moz.build2
-rw-r--r--dom/plugins/base/moz.build6
-rw-r--r--dom/plugins/base/nsPluginsDirDarwin.cpp572
-rw-r--r--dom/system/mac/CoreLocationLocationProvider.h59
-rw-r--r--dom/system/mac/CoreLocationLocationProvider.mm268
-rw-r--r--dom/system/mac/moz.build14
-rw-r--r--dom/system/moz.build2
-rw-r--r--dom/xbl/builtin/mac/jar.mn6
-rw-r--r--dom/xbl/builtin/mac/moz.build6
-rw-r--r--dom/xbl/builtin/mac/platformHTMLBindings.xml72
-rw-r--r--dom/xbl/builtin/moz.build2
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: