2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
4 /* vim: set ts=2 et sw=2 tw=80: */
5 /* This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
7 * You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "xpcAccessibleMacInterface.h"
11 #include "nsCocoaUtils.h"
12 #include "nsContentUtils.h"
13 #include "nsIObserverService.h"
14 #include "nsISimpleEnumerator.h"
15 #include "nsIXPConnect.h"
16 #include "mozilla/dom/ToJSValue.h"
17 #include "mozilla/Services.h"
19 #include "js/PropertyAndElement.h" // JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasOwnProperty, JS_SetUCProperty
21 #import "mozAccessible.h"
23 using namespace mozilla::a11y;
25 // xpcAccessibleMacNSObjectWrapper
27 NS_IMPL_ISUPPORTS(xpcAccessibleMacNSObjectWrapper,
28 nsIAccessibleMacNSObjectWrapper)
30 xpcAccessibleMacNSObjectWrapper::xpcAccessibleMacNSObjectWrapper(id aNativeObj)
31 : mNativeObject(aNativeObj) {
32 [mNativeObject retain];
35 xpcAccessibleMacNSObjectWrapper::~xpcAccessibleMacNSObjectWrapper() {
36 [mNativeObject release];
39 id xpcAccessibleMacNSObjectWrapper::GetNativeObject() { return mNativeObject; }
41 // xpcAccessibleMacInterface
43 NS_IMPL_ISUPPORTS_INHERITED(xpcAccessibleMacInterface,
44 xpcAccessibleMacNSObjectWrapper,
45 nsIAccessibleMacInterface)
47 xpcAccessibleMacInterface::xpcAccessibleMacInterface(Accessible* aObj)
48 : xpcAccessibleMacNSObjectWrapper(GetNativeFromGeckoAccessible(aObj)) {}
51 xpcAccessibleMacInterface::GetAttributeNames(
52 nsTArray<nsString>& aAttributeNames) {
53 NS_OBJC_BEGIN_TRY_BLOCK_RETURN
55 if (!mNativeObject || [mNativeObject isExpired]) {
56 return NS_ERROR_NOT_AVAILABLE;
59 for (NSString* name in [mNativeObject accessibilityAttributeNames]) {
60 nsAutoString attribName;
61 nsCocoaUtils::GetStringForNSString(name, attribName);
62 aAttributeNames.AppendElement(attribName);
67 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
71 xpcAccessibleMacInterface::GetParameterizedAttributeNames(
72 nsTArray<nsString>& aAttributeNames) {
73 NS_OBJC_BEGIN_TRY_BLOCK_RETURN
75 if (!mNativeObject || [mNativeObject isExpired]) {
76 return NS_ERROR_NOT_AVAILABLE;
79 for (NSString* name in
80 [mNativeObject accessibilityParameterizedAttributeNames]) {
81 nsAutoString attribName;
82 nsCocoaUtils::GetStringForNSString(name, attribName);
83 aAttributeNames.AppendElement(attribName);
88 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
92 xpcAccessibleMacInterface::GetActionNames(nsTArray<nsString>& aActionNames) {
93 NS_OBJC_BEGIN_TRY_BLOCK_RETURN
95 if (!mNativeObject || [mNativeObject isExpired]) {
96 return NS_ERROR_NOT_AVAILABLE;
99 for (NSString* name in [mNativeObject accessibilityActionNames]) {
100 nsAutoString actionName;
101 nsCocoaUtils::GetStringForNSString(name, actionName);
102 aActionNames.AppendElement(actionName);
107 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
111 xpcAccessibleMacInterface::PerformAction(const nsAString& aActionName) {
112 NS_OBJC_BEGIN_TRY_BLOCK_RETURN
114 if (!mNativeObject || [mNativeObject isExpired]) {
115 return NS_ERROR_NOT_AVAILABLE;
118 NSString* actionName = nsCocoaUtils::ToNSString(aActionName);
119 [mNativeObject accessibilityPerformAction:actionName];
123 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
127 xpcAccessibleMacInterface::GetAttributeValue(const nsAString& aAttributeName,
129 JS::MutableHandleValue aResult) {
130 NS_OBJC_BEGIN_TRY_BLOCK_RETURN
132 if (!mNativeObject || [mNativeObject isExpired]) {
133 return NS_ERROR_NOT_AVAILABLE;
136 NSString* attribName = nsCocoaUtils::ToNSString(aAttributeName);
137 return NSObjectToJsValue(
138 [mNativeObject accessibilityAttributeValue:attribName], aCx, aResult);
140 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
144 xpcAccessibleMacInterface::IsAttributeSettable(const nsAString& aAttributeName,
146 NS_ENSURE_ARG_POINTER(aIsSettable);
148 NSString* attribName = nsCocoaUtils::ToNSString(aAttributeName);
150 respondsToSelector:@selector(accessibilityIsAttributeSettable:)]) {
151 *aIsSettable = [mNativeObject accessibilityIsAttributeSettable:attribName];
155 return NS_ERROR_NOT_IMPLEMENTED;
159 xpcAccessibleMacInterface::SetAttributeValue(const nsAString& aAttributeName,
160 JS::HandleValue aAttributeValue,
163 id obj = JsValueToNSObject(aAttributeValue, aCx, &rv);
164 NS_ENSURE_SUCCESS(rv, rv);
166 NSString* attribName = nsCocoaUtils::ToNSString(aAttributeName);
167 if ([mNativeObject respondsToSelector:@selector(accessibilitySetValue:
169 // The NSObject has an attribute setter, call that.
170 [mNativeObject accessibilitySetValue:obj forAttribute:attribName];
174 return NS_ERROR_NOT_IMPLEMENTED;
178 xpcAccessibleMacInterface::GetParameterizedAttributeValue(
179 const nsAString& aAttributeName, JS::HandleValue aParameter, JSContext* aCx,
180 JS::MutableHandleValue aResult) {
182 id paramObj = JsValueToNSObject(aParameter, aCx, &rv);
183 NS_ENSURE_SUCCESS(rv, rv);
185 NSString* attribName = nsCocoaUtils::ToNSString(aAttributeName);
186 return NSObjectToJsValue([mNativeObject accessibilityAttributeValue:attribName
187 forParameter:paramObj],
191 bool xpcAccessibleMacInterface::SupportsSelector(SEL aSelector) {
192 // return true if we have this selector, and if isAccessibilitySelectorAllowed
193 // is implemented too whether it is "allowed".
194 return [mNativeObject respondsToSelector:aSelector] &&
195 (![mNativeObject respondsToSelector:@selector
196 (isAccessibilitySelectorAllowed:selector:)] ||
197 [mNativeObject isAccessibilitySelectorAllowed:aSelector]);
200 nsresult xpcAccessibleMacInterface::NSObjectToJsValue(
201 id aObj, JSContext* aCx, JS::MutableHandleValue aResult) {
203 aResult.set(JS::NullValue());
204 } else if ([aObj isKindOfClass:[NSString class]]) {
206 nsCocoaUtils::GetStringForNSString((NSString*)aObj, strVal);
207 if (!mozilla::dom::ToJSValue(aCx, strVal, aResult)) {
208 return NS_ERROR_FAILURE;
210 } else if ([aObj isKindOfClass:[NSNumber class]]) {
211 // If the type being held by the NSNumber is a BOOL set js value
212 // to boolean. Otherwise use a double value.
213 if (strcmp([(NSNumber*)aObj objCType], @encode(BOOL)) == 0) {
214 if (!mozilla::dom::ToJSValue(aCx, [(NSNumber*)aObj boolValue], aResult)) {
215 return NS_ERROR_FAILURE;
218 if (!mozilla::dom::ToJSValue(aCx, [(NSNumber*)aObj doubleValue],
220 return NS_ERROR_FAILURE;
223 } else if ([aObj isKindOfClass:[NSValue class]] &&
224 strcmp([(NSValue*)aObj objCType], @encode(NSPoint)) == 0) {
225 NSPoint point = [(NSValue*)aObj pointValue];
226 return NSObjectToJsValue(
228 [NSNumber numberWithDouble:point.x],
229 [NSNumber numberWithDouble:point.y]
232 } else if ([aObj isKindOfClass:[NSValue class]] &&
233 strcmp([(NSValue*)aObj objCType], @encode(NSSize)) == 0) {
234 NSSize size = [(NSValue*)aObj sizeValue];
235 return NSObjectToJsValue(
237 [NSNumber numberWithDouble:size.width],
238 [NSNumber numberWithDouble:size.height]
241 } else if ([aObj isKindOfClass:[NSValue class]] &&
242 strcmp([(NSValue*)aObj objCType], @encode(NSRange)) == 0) {
243 NSRange range = [(NSValue*)aObj rangeValue];
244 return NSObjectToJsValue(@[ @(range.location), @(range.length) ], aCx,
246 } else if ([aObj isKindOfClass:[NSValue class]] &&
247 strcmp([(NSValue*)aObj objCType], @encode(NSRect)) == 0) {
248 NSRect rect = [(NSValue*)aObj rectValue];
249 return NSObjectToJsValue(@{
250 @"origin" : [NSValue valueWithPoint:rect.origin],
251 @"size" : [NSValue valueWithSize:rect.size]
254 } else if ([aObj isKindOfClass:[NSArray class]]) {
255 NSArray* objArr = (NSArray*)aObj;
257 JS::RootedVector<JS::Value> v(aCx);
258 if (!v.resize([objArr count])) {
259 return NS_ERROR_FAILURE;
261 for (size_t i = 0; i < [objArr count]; ++i) {
262 nsresult rv = NSObjectToJsValue(objArr[i], aCx, v[i]);
263 NS_ENSURE_SUCCESS(rv, rv);
266 JSObject* arrayObj = JS::NewArrayObject(aCx, v);
268 return NS_ERROR_FAILURE;
270 aResult.setObject(*arrayObj);
271 } else if ([aObj isKindOfClass:[NSDictionary class]]) {
272 JS::RootedObject obj(aCx, JS_NewPlainObject(aCx));
273 for (NSString* key in aObj) {
275 nsCocoaUtils::GetStringForNSString(key, strKey);
276 JS::RootedValue value(aCx);
277 nsresult rv = NSObjectToJsValue(aObj[key], aCx, &value);
278 NS_ENSURE_SUCCESS(rv, rv);
279 JS_SetUCProperty(aCx, obj, strKey.get(), strKey.Length(), value);
281 aResult.setObject(*obj);
282 } else if ([aObj isKindOfClass:[NSAttributedString class]]) {
283 NSAttributedString* attrStr = (NSAttributedString*)aObj;
284 __block NSMutableArray* attrRunArray = [[NSMutableArray alloc] init];
287 enumerateAttributesInRange:NSMakeRange(0, [attrStr length])
289 NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
290 usingBlock:^(NSDictionary* attributes, NSRange range,
293 [[attrStr string] substringWithRange:range];
294 if (!str || !attributes) {
298 NSMutableDictionary* attrRun =
299 [attributes mutableCopy];
300 attrRun[@"string"] = str;
302 [attrRunArray addObject:attrRun];
305 // The attributed string is represented in js as an array of objects.
306 // Each object represents a run of text where the "string" property is the
307 // string value and all the AX* properties are the attributes.
308 return NSObjectToJsValue(attrRunArray, aCx, aResult);
309 } else if (CFGetTypeID(aObj) == CGColorGetTypeID()) {
310 const CGFloat* components = CGColorGetComponents((CGColorRef)aObj);
311 NSString* hexString = [NSString
312 stringWithFormat:@"#%02x%02x%02x", (int)(components[0] * 0xff),
313 (int)(components[1] * 0xff),
314 (int)(components[2] * 0xff)];
315 return NSObjectToJsValue(hexString, aCx, aResult);
316 } else if ([aObj respondsToSelector:@selector(isAccessibilityElement)]) {
317 // We expect all of our accessibility objects to implement
318 // isAccessibilityElement at the very least. If it is implemented we will
319 // assume its an accessibility object.
320 nsCOMPtr<nsIAccessibleMacInterface> obj =
321 new xpcAccessibleMacInterface(aObj);
322 return nsContentUtils::WrapNative(
323 aCx, obj, &NS_GET_IID(nsIAccessibleMacInterface), aResult);
325 // If this is any other kind of NSObject, just wrap it and return it.
326 // It will be opaque and immutable on the JS side, but it can be
327 // brought back to us in an argument.
328 nsCOMPtr<nsIAccessibleMacNSObjectWrapper> obj =
329 new xpcAccessibleMacNSObjectWrapper(aObj);
330 return nsContentUtils::WrapNative(
331 aCx, obj, &NS_GET_IID(nsIAccessibleMacNSObjectWrapper), aResult);
337 id xpcAccessibleMacInterface::JsValueToNSObject(JS::HandleValue aValue,
341 if (aValue.isInt32()) {
342 return [NSNumber numberWithInteger:aValue.toInt32()];
343 } else if (aValue.isBoolean()) {
344 return [NSNumber numberWithBool:aValue.toBoolean()];
345 } else if (aValue.isString()) {
347 if (!temp.init(aCx, aValue)) {
348 NS_WARNING("cannot init string with given value");
349 *aResult = NS_ERROR_FAILURE;
352 return nsCocoaUtils::ToNSString(temp);
353 } else if (aValue.isObject()) {
354 JS::Rooted<JSObject*> obj(aCx, aValue.toObjectOrNull());
357 JS::IsArrayObject(aCx, obj, &isArray);
359 // If this is an array, we construct an NSArray and insert the js
360 // array's elements by recursively calling this function.
362 JS::GetArrayLength(aCx, obj, &len);
363 NSMutableArray* array = [NSMutableArray arrayWithCapacity:len];
364 for (uint32_t i = 0; i < len; i++) {
365 JS::RootedValue v(aCx);
366 JS_GetElement(aCx, obj, i, &v);
367 [array addObject:JsValueToNSObject(v, aCx, aResult)];
368 NS_ENSURE_SUCCESS(*aResult, nil);
375 JS_HasOwnProperty(aCx, obj, "valueType", &hasValueType);
376 JS_HasOwnProperty(aCx, obj, "value", &hasValue);
377 if (hasValueType && hasValue) {
378 // A js object representin an NSValue looks like this:
379 // { valueType: "NSRange", value: [1, 3] }
380 return JsValueToNSValue(obj, aCx, aResult);
385 JS_HasOwnProperty(aCx, obj, "objectType", &hasObjectType);
386 JS_HasOwnProperty(aCx, obj, "object", &hasObject);
387 if (hasObjectType && hasObject) {
388 // A js object representing an NSDictionary looks like this:
389 // { objectType: "NSDictionary", value: {k: v, k: v, ...} }
390 return JsValueToSpecifiedNSObject(obj, aCx, aResult);
393 // This may be another nsIAccessibleMacInterface instance.
394 // If so, return the wrapped NSObject.
395 nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
397 nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
399 xpc->GetWrappedNativeOfJSObject(aCx, obj, getter_AddRefs(wrappedObj));
400 NS_ENSURE_SUCCESS(rv, nil);
401 nsCOMPtr<nsIAccessibleMacNSObjectWrapper> macObjIface =
402 do_QueryInterface(wrappedObj->Native());
403 return macObjIface->GetNativeObject();
406 *aResult = NS_ERROR_FAILURE;
410 id xpcAccessibleMacInterface::JsValueToNSValue(JS::HandleObject aObject,
413 *aResult = NS_ERROR_FAILURE;
414 JS::RootedValue valueTypeValue(aCx);
415 if (!JS_GetProperty(aCx, aObject, "valueType", &valueTypeValue)) {
416 NS_WARNING("Could not get valueType");
420 JS::RootedValue valueValue(aCx);
421 if (!JS_GetProperty(aCx, aObject, "value", &valueValue)) {
422 NS_WARNING("Could not get value");
426 nsAutoJSString valueType;
427 if (!valueTypeValue.isString() || !valueType.init(aCx, valueTypeValue)) {
428 NS_WARNING("valueType is not a string");
433 JS::IsArrayObject(aCx, valueValue, &isArray);
435 NS_WARNING("value is not an array");
439 JS::Rooted<JSObject*> value(aCx, valueValue.toObjectOrNull());
441 if (valueType.EqualsLiteral("NSRange")) {
443 JS::GetArrayLength(aCx, value, &len);
445 NS_WARNING("Expected a 2 member array");
449 JS::RootedValue locationValue(aCx);
450 JS_GetElement(aCx, value, 0, &locationValue);
451 JS::RootedValue lengthValue(aCx);
452 JS_GetElement(aCx, value, 1, &lengthValue);
453 if (!locationValue.isInt32() || !lengthValue.isInt32()) {
454 NS_WARNING("Expected an array of integers");
459 return [NSValue valueWithRange:NSMakeRange(locationValue.toInt32(),
460 lengthValue.toInt32())];
466 id xpcAccessibleMacInterface::JsValueToSpecifiedNSObject(
467 JS::HandleObject aObject, JSContext* aCx, nsresult* aResult) {
468 *aResult = NS_ERROR_FAILURE;
469 JS::RootedValue objectTypeValue(aCx);
470 if (!JS_GetProperty(aCx, aObject, "objectType", &objectTypeValue)) {
471 NS_WARNING("Could not get objectType");
475 JS::RootedValue objectValue(aCx);
476 if (!JS_GetProperty(aCx, aObject, "object", &objectValue)) {
477 NS_WARNING("Could not get object");
481 nsAutoJSString objectType;
482 if (!objectTypeValue.isString()) {
483 NS_WARNING("objectType is not a string");
487 if (!objectType.init(aCx, objectTypeValue)) {
488 NS_WARNING("cannot init string with object type");
492 bool isObject = objectValue.isObjectOrNull();
494 NS_WARNING("object is not a JSON object");
498 JS::Rooted<JSObject*> object(aCx, objectValue.toObjectOrNull());
500 if (objectType.EqualsLiteral("NSDictionary")) {
501 JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
502 if (!JS_Enumerate(aCx, object, &ids)) {
503 NS_WARNING("Unable to get keys from dictionary object");
507 NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
509 for (size_t i = 0, n = ids.length(); i < n; i++) {
512 JS::RootedValue currentKey(aCx);
513 JS_IdToValue(aCx, ids[i], ¤tKey);
514 id unwrappedKey = JsValueToNSObject(currentKey, aCx, &rv);
515 NS_ENSURE_SUCCESS(rv, nil);
516 MOZ_ASSERT([unwrappedKey isKindOfClass:[NSString class]]);
518 // get associated value for current key
519 JS::RootedValue currentValue(aCx);
520 JS_GetPropertyById(aCx, object, ids[i], ¤tValue);
521 id unwrappedValue = JsValueToNSObject(currentValue, aCx, &rv);
522 NS_ENSURE_SUCCESS(rv, nil);
523 dict[unwrappedKey] = unwrappedValue;
533 NS_IMPL_ISUPPORTS(xpcAccessibleMacEvent, nsIAccessibleMacEvent)
535 xpcAccessibleMacEvent::xpcAccessibleMacEvent(id aNativeObj, id aData)
536 : mNativeObject(aNativeObj), mData(aData) {
537 [mNativeObject retain];
541 xpcAccessibleMacEvent::~xpcAccessibleMacEvent() {
542 [mNativeObject release];
547 xpcAccessibleMacEvent::GetMacIface(nsIAccessibleMacInterface** aMacIface) {
548 RefPtr<xpcAccessibleMacInterface> macIface =
549 new xpcAccessibleMacInterface(mNativeObject);
550 macIface.forget(aMacIface);
555 xpcAccessibleMacEvent::GetData(JSContext* aCx, JS::MutableHandleValue aData) {
556 return xpcAccessibleMacInterface::NSObjectToJsValue(mData, aCx, aData);
559 void xpcAccessibleMacEvent::FireEvent(id aNativeObj, id aNotification,
561 if (nsCOMPtr<nsIObserverService> obsService =
562 services::GetObserverService()) {
563 nsCOMPtr<nsISimpleEnumerator> observers;
564 // Get all observers for the mac event topic.
565 obsService->EnumerateObservers(NS_ACCESSIBLE_MAC_EVENT_TOPIC,
566 getter_AddRefs(observers));
568 bool hasObservers = false;
569 observers->HasMoreElements(&hasObservers);
570 // If we have observers, notify them.
572 nsCOMPtr<nsIAccessibleMacEvent> xpcIface =
573 new xpcAccessibleMacEvent(aNativeObj, aUserInfo);
574 nsAutoString notificationStr;
575 nsCocoaUtils::GetStringForNSString(aNotification, notificationStr);
576 obsService->NotifyObservers(xpcIface, NS_ACCESSIBLE_MAC_EVENT_TOPIC,
577 notificationStr.get());