Source/JavaScriptCore:
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Jun 2015 20:51:13 +0000 (20:51 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Jun 2015 20:51:13 +0000 (20:51 +0000)
Bug 146177 - AX: AXObjectCache should try to use an unignored accessibilityObject
when posting a selection notification when on the border between two accessibilityObjects
https://bugs.webkit.org/show_bug.cgi?id=146177

Add an adopt() function to simplify JSRetainPtr<JSStringRef> { Adopt, string } to adopt(string).

Patch by Doug Russell <d_russell@apple.com> on 2015-06-24
Reviewed by Darin Adler.

* API/JSRetainPtr.h:
(adopt):

Source/WebCore:
Bug 146177 - AX: AXObjectCache should try to use an unignored accessibilityObject
when posting a selection notification when on the border between two accessibilityObjects
https://bugs.webkit.org/show_bug.cgi?id=146177

Patch by Doug Russell <d_russell@apple.com> on 2015-06-24
Reviewed by Darin Adler.

Add support for position to be passed for selection changes to allow checking.
for boundaries in the case of ignored elements.
Add support for searching for unignored siblings of AccessibilityObjects.
Include AccessibilityObject wrappers in notifications for tests.

Test: platform/mac/accessibility/selection-element-tabbing-to-link.html

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::postTextStateChangeNotification):
* accessibility/AXObjectCache.h:
* accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::previousSiblingUnignored):
(WebCore::AccessibilityObject::nextSiblingUnignored):
* accessibility/AccessibilityObject.h:
* accessibility/mac/WebAccessibilityObjectWrapperBase.mm:
(isValueTypeSupported):
(arrayRemovingNonSupportedTypes):
(dictionaryRemovingNonSupportedTypes):
(-[WebAccessibilityObjectWrapperBase accessibilityPostedNotification:userInfo:]):
* editing/mac/FrameSelectionMac.mm:
(WebCore::FrameSelection::notifyAccessibilityForSelectionChange):

Tools:
Bug 146177 - AX: AXObjectCache should try to use an unignored accessibilityObject
when posting a selection notification when on the border between two accessibilityObjects
https://bugs.webkit.org/show_bug.cgi?id=146177

Patch by Doug Russell <d_russell@apple.com> on 2015-06-24
Reviewed by Darin Adler.

Add support for mapping WebCore Accessibility types into JSValueRef types
so they can be used in tests.

* DumpRenderTree/mac/AccessibilityNotificationHandler.mm:
(webAccessibilityObjectWrapperClass):
(-[AccessibilityNotificationHandler startObserving]):
(makeValueRefForValue):
(makeArrayRefForArray):
(makeObjectRefForDictionary):
(-[AccessibilityNotificationHandler _notificationReceived:]):
* WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:
(webAccessibilityObjectWrapperClass):
(-[AccessibilityNotificationHandler startObserving]):
(makeValueRefForValue):
(makeArrayRefForArray):
(makeObjectRefForDictionary):
(-[AccessibilityNotificationHandler _notificationReceived:]):

LayoutTests:
Bug 146177 - AX: AXObjectCache should try to use an unignored accessibilityObject
when posting a selection notification when on the border between two accessibilityObjects
https://bugs.webkit.org/show_bug.cgi?id=146177

Add test for forward and backward tabbing between links and corresponding notifications
change element

Patch by Doug Russell <d_russell@apple.com> on 2015-06-24
Reviewed by Darin Adler.

* platform/mac/accessibility/selection-element-tabbing-to-link-expected.txt: Added.
* platform/mac/accessibility/selection-element-tabbing-to-link.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@185924 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
LayoutTests/ChangeLog
LayoutTests/platform/mac/accessibility/selection-element-tabbing-to-link-expected.txt [new file with mode: 0644]
LayoutTests/platform/mac/accessibility/selection-element-tabbing-to-link.html [new file with mode: 0644]
Source/JavaScriptCore/API/JSRetainPtr.h
Source/JavaScriptCore/ChangeLog
Source/WebCore/ChangeLog
Source/WebCore/accessibility/AXObjectCache.cpp
Source/WebCore/accessibility/AXObjectCache.h
Source/WebCore/accessibility/AccessibilityObject.cpp
Source/WebCore/accessibility/AccessibilityObject.h
Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperBase.mm
Source/WebCore/editing/mac/FrameSelectionMac.mm
Tools/ChangeLog
Tools/DumpRenderTree/mac/AccessibilityNotificationHandler.mm
Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm

index fcfa711..7153eef 100644 (file)
@@ -1,3 +1,17 @@
+2015-06-24  Doug Russell  <d_russell@apple.com>
+
+        Bug 146177 - AX: AXObjectCache should try to use an unignored accessibilityObject
+        when posting a selection notification when on the border between two accessibilityObjects
+        https://bugs.webkit.org/show_bug.cgi?id=146177
+
+        Add test for forward and backward tabbing between links and corresponding notifications
+        change element
+
+        Reviewed by Darin Adler.
+
+        * platform/mac/accessibility/selection-element-tabbing-to-link-expected.txt: Added.
+        * platform/mac/accessibility/selection-element-tabbing-to-link.html: Added.
+
 2015-06-24  Keith Miller  <keith_miller@apple.com>
 
         Strict Equality on objects should only check that one of the two sides is an object.
diff --git a/LayoutTests/platform/mac/accessibility/selection-element-tabbing-to-link-expected.txt b/LayoutTests/platform/mac/accessibility/selection-element-tabbing-to-link-expected.txt
new file mode 100644 (file)
index 0000000..8ff09b5
--- /dev/null
@@ -0,0 +1,13 @@
+one two
+This tests that tabbing between links includes a relevant accessibilityObject in the userInfo when on the boundary between an ignored accessibilityObject and an unignored accessibilityObject.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS addedNotification is true
+PASS ancestorWithRole(changeElementOne, "AXRole: AXLink").isEqual(linkTwo) is true
+PASS ancestorWithRole(changeElementTwo, "AXRole: AXLink").isEqual(linkOne) is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/platform/mac/accessibility/selection-element-tabbing-to-link.html b/LayoutTests/platform/mac/accessibility/selection-element-tabbing-to-link.html
new file mode 100644 (file)
index 0000000..34eb50c
--- /dev/null
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src="../../../resources/js-test-pre.js"></script>
+</head>
+<body id="body">
+
+<div>
+<a href="" id="one">one</a>
+<a href="" id="two">two</a>
+</div>
+
+<p id="description"></p>
+<div id="console"></div>
+<div id="notifications"></div>
+
+<script>
+
+    description("This tests that tabbing between links includes a relevant accessibilityObject in the userInfo when on the boundary between an ignored accessibilityObject and an unignored accessibilityObject.");
+
+    var axTextChangeElement = 0;
+    var webArea = 0;
+    var changeElementOne = 0;
+    var changeElementTwo = 0;
+    var linkTwo = 0;
+    var linkOne = 0;
+    var count = 0;
+
+    function ancestorWithRole(axElement, role) {
+        var ancestor = axElement;
+        while (ancestor) {
+            if (ancestor.role == role)
+                break;
+            ancestor = ancestor.parentElement();
+        }
+        return ancestor;
+    }
+
+    function notificationCallback(notification, userInfo) {
+        if (notification == "AXSelectedTextChanged") {
+            count++;
+            if (count == 1) {
+                changeElementOne = userInfo["AXTextChangeElement"];
+                linkTwo = accessibilityController.accessibleElementById("two");
+                shouldBe("ancestorWithRole(changeElementOne, \"AXRole: AXLink\").isEqual(linkTwo)", "true");
+            } else if (count == 2) {
+                changeElementTwo = userInfo["AXTextChangeElement"];
+                linkOne = accessibilityController.accessibleElementById("one");
+                shouldBe("ancestorWithRole(changeElementTwo, \"AXRole: AXLink\").isEqual(linkOne)", "true");
+                webArea.removeNotificationListener();
+                testRunner.notifyDone();
+            }
+        }
+    }
+
+    if (window.accessibilityController) {
+        testRunner.waitUntilDone();
+        testRunner.overridePreference("WebKitTabToLinksPreferenceKey", 1);
+
+        accessibilityController.enableEnhancedAccessibility(true);
+        webArea = accessibilityController.rootElement.childAtIndex(0);
+        webArea.setBoolAttributeValue("AXCaretBrowsingEnabled", true)
+
+        link = document.getElementById("one");
+        link.focus();
+
+        var addedNotification = webArea.addNotificationListener(notificationCallback);
+        shouldBe("addedNotification", "true");
+
+        eventSender.keyDown("\t");
+        eventSender.keyDown("\t", ["shiftKey"]);
+    }
+
+</script>
+
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
index f23e32f..262c4d5 100644 (file)
@@ -75,6 +75,16 @@ private:
     T m_ptr;
 };
 
+inline JSRetainPtr<JSStringRef> adopt(JSStringRef o)
+{
+    return JSRetainPtr<JSStringRef>(Adopt, o);
+}
+
+inline JSRetainPtr<JSGlobalContextRef> adopt(JSGlobalContextRef o)
+{
+    return JSRetainPtr<JSGlobalContextRef>(Adopt, o);
+}
+
 template<typename T> inline JSRetainPtr<T>::JSRetainPtr(const JSRetainPtr& o)
     : m_ptr(o.m_ptr)
 {
index 23064bf..c9dab00 100644 (file)
@@ -1,3 +1,16 @@
+2015-06-24  Doug Russell  <d_russell@apple.com>
+
+        Bug 146177 - AX: AXObjectCache should try to use an unignored accessibilityObject 
+        when posting a selection notification when on the border between two accessibilityObjects
+        https://bugs.webkit.org/show_bug.cgi?id=146177
+
+        Add an adopt() function to simplify JSRetainPtr<JSStringRef> { Adopt, string } to adopt(string).
+
+        Reviewed by Darin Adler.
+
+        * API/JSRetainPtr.h:
+        (adopt):
+
 2015-06-24  Keith Miller  <keith_miller@apple.com>
 
         Strict Equality on objects should only check that one of the two sides is an object.
index e366811..68985bf 100644 (file)
@@ -1,3 +1,33 @@
+2015-06-24  Doug Russell  <d_russell@apple.com>
+
+        Bug 146177 - AX: AXObjectCache should try to use an unignored accessibilityObject 
+        when posting a selection notification when on the border between two accessibilityObjects
+        https://bugs.webkit.org/show_bug.cgi?id=146177
+
+        Reviewed by Darin Adler.
+
+        Add support for position to be passed for selection changes to allow checking.
+        for boundaries in the case of ignored elements.
+        Add support for searching for unignored siblings of AccessibilityObjects.
+        Include AccessibilityObject wrappers in notifications for tests.
+
+        Test: platform/mac/accessibility/selection-element-tabbing-to-link.html
+
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::AXObjectCache::postTextStateChangeNotification):
+        * accessibility/AXObjectCache.h:
+        * accessibility/AccessibilityObject.cpp:
+        (WebCore::AccessibilityObject::previousSiblingUnignored):
+        (WebCore::AccessibilityObject::nextSiblingUnignored):
+        * accessibility/AccessibilityObject.h:
+        * accessibility/mac/WebAccessibilityObjectWrapperBase.mm:
+        (isValueTypeSupported):
+        (arrayRemovingNonSupportedTypes):
+        (dictionaryRemovingNonSupportedTypes):
+        (-[WebAccessibilityObjectWrapperBase accessibilityPostedNotification:userInfo:]):
+        * editing/mac/FrameSelectionMac.mm:
+        (WebCore::FrameSelection::notifyAccessibilityForSelectionChange):
+
 2015-06-24  Anders Carlsson  <andersca@apple.com>
 
         Enable -Winconsistent-missing-override when building with Xcode
index 47750dd..cdfba63 100644 (file)
@@ -1006,16 +1006,56 @@ void AXObjectCache::postTextStateChangeNotification(Node* node, const AXTextStat
 #if PLATFORM(COCOA)
     stopCachingComputedObjectAttributes();
 
+    postTextStateChangeNotification(getOrCreate(node), intent, selection);
+#else
+    postNotification(node->renderer(), AXObjectCache::AXSelectedTextChanged, TargetObservableParent);
+    UNUSED_PARAM(intent);
+    UNUSED_PARAM(selection);
+#endif
+}
+
+void AXObjectCache::postTextStateChangeNotification(const Position& position, const AXTextStateChangeIntent& intent, const VisibleSelection& selection)
+{
+    Node* node = position.deprecatedNode();
+    if (!node)
+        return;
+
+    stopCachingComputedObjectAttributes();
+
+#if PLATFORM(COCOA)
     AccessibilityObject* object = getOrCreate(node);
+    if (object && object->accessibilityIsIgnored()) {
+        if (position.atLastEditingPositionForNode()) {
+            if (AccessibilityObject* nextSibling = object->nextSiblingUnignored(1))
+                object = nextSibling;
+        } else if (position.atFirstEditingPositionForNode()) {
+            if (AccessibilityObject* previousSibling = object->previousSiblingUnignored(1))
+                object = previousSibling;
+        }
+    }
+
+    postTextStateChangeNotification(object, intent, selection);
+#else
+    postTextStateChangeNotification(node, intent, selection);
+#endif
+}
+
+void AXObjectCache::postTextStateChangeNotification(AccessibilityObject* object, const AXTextStateChangeIntent& intent, const VisibleSelection& selection)
+{
+    stopCachingComputedObjectAttributes();
+
+#if PLATFORM(COCOA)
     if (object) {
         if (isPasswordFieldOrContainedByPasswordField(object))
             return;
-        object = object->observableObject();
+
+        if (auto observableObject = object->observableObject())
+            object = observableObject;
     }
 
     postTextStateChangePlatformNotification(object, (intent.type == AXTextStateChangeTypeUnknown || m_isSynchronizingSelection) ? m_textSelectionIntent : intent, selection);
 #else
-    postNotification(node->renderer(), AXObjectCache::AXSelectedTextChanged, TargetObservableParent);
+    UNUSED_PARAM(object);
     UNUSED_PARAM(intent);
     UNUSED_PARAM(selection);
 #endif
index 67e07cf..f2aabbd 100644 (file)
@@ -205,6 +205,7 @@ public:
     void postTextStateChangeNotification(Node*, AXTextEditType, const String&, const VisiblePosition&);
     void postTextReplacementNotification(Node*, AXTextEditType deletionType, const String& deletedText, AXTextEditType insertionType, const String& insertedText, const VisiblePosition&);
     void postTextStateChangeNotification(Node*, const AXTextStateChangeIntent&, const VisibleSelection&);
+    void postTextStateChangeNotification(const Position&, const AXTextStateChangeIntent&, const VisibleSelection&);
 
     enum AXLoadingEvent {
         AXLoadingStarted,
@@ -259,6 +260,8 @@ private:
 
     void notificationPostTimerFired();
 
+    void postTextStateChangeNotification(AccessibilityObject*, const AXTextStateChangeIntent&, const VisibleSelection&);
+
     bool enqueuePasswordValueChangeNotification(AccessibilityObject*);
     void passwordNotificationPostTimerFired();
 
index 34e63d6..f74b755 100644 (file)
@@ -457,6 +457,30 @@ AccessibilityObject* AccessibilityObject::parentObjectUnignored() const
     return parent;
 }
 
+AccessibilityObject* AccessibilityObject::previousSiblingUnignored(int limit) const
+{
+    AccessibilityObject* previous;
+    ASSERT(limit >= 0);
+    for (previous = previousSibling(); previous && previous->accessibilityIsIgnored(); previous = previous->previousSibling()) {
+        limit--;
+        if (limit <= 0)
+            break;
+    }
+    return previous;
+}
+
+AccessibilityObject* AccessibilityObject::nextSiblingUnignored(int limit) const
+{
+    AccessibilityObject* next;
+    ASSERT(limit >= 0);
+    for (next = nextSibling(); next && next->accessibilityIsIgnored(); next = next->nextSibling()) {
+        limit--;
+        if (limit <= 0)
+            break;
+    }
+    return next;
+}
+
 AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node)
 {
     if (!node)
index 6cbd1a0..102db9a 100644 (file)
@@ -645,6 +645,8 @@ public:
     virtual AccessibilityObject* lastChild() const { return nullptr; }
     virtual AccessibilityObject* previousSibling() const { return nullptr; }
     virtual AccessibilityObject* nextSibling() const { return nullptr; }
+    virtual AccessibilityObject* nextSiblingUnignored(int limit) const;
+    virtual AccessibilityObject* previousSiblingUnignored(int limit) const;
     virtual AccessibilityObject* parentObject() const = 0;
     virtual AccessibilityObject* parentObjectUnignored() const;
     virtual AccessibilityObject* parentObjectIfExists() const { return nullptr; }
index e8b2ef0..93534c2 100644 (file)
@@ -394,17 +394,22 @@ static BOOL accessibilityShouldRepostNotifications;
         [self accessibilityPostedNotification:notificationName userInfo:nil];
 }
 
-static NSArray *arrayRemovingNonJSONTypes(NSArray *array)
+static bool isValueTypeSupported(id value)
+{
+    return [value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[WebAccessibilityObjectWrapperBase class]];
+}
+
+static NSArray *arrayRemovingNonSupportedTypes(NSArray *array)
 {
     ASSERT([array isKindOfClass:[NSArray class]]);
     NSMutableArray *mutableArray = [array mutableCopy];
     for (NSUInteger i = 0; i < [mutableArray count];) {
         id value = [mutableArray objectAtIndex:i];
         if ([value isKindOfClass:[NSDictionary class]])
-            [mutableArray replaceObjectAtIndex:i withObject:dictionaryRemovingNonJSONTypes(value)];
+            [mutableArray replaceObjectAtIndex:i withObject:dictionaryRemovingNonSupportedTypes(value)];
         else if ([value isKindOfClass:[NSArray class]])
-            [mutableArray replaceObjectAtIndex:i withObject:arrayRemovingNonJSONTypes(value)];
-        else if (!([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])) {
+            [mutableArray replaceObjectAtIndex:i withObject:arrayRemovingNonSupportedTypes(value)];
+        else if (!isValueTypeSupported(value)) {
             [mutableArray removeObjectAtIndex:i];
             continue;
         }
@@ -413,17 +418,19 @@ static NSArray *arrayRemovingNonJSONTypes(NSArray *array)
     return [mutableArray autorelease];
 }
 
-static NSDictionary *dictionaryRemovingNonJSONTypes(NSDictionary *dictionary)
+static NSDictionary *dictionaryRemovingNonSupportedTypes(NSDictionary *dictionary)
 {
+    if (!dictionary)
+        return nil;
     ASSERT([dictionary isKindOfClass:[NSDictionary class]]);
     NSMutableDictionary *mutableDictionary = [dictionary mutableCopy];
     for (NSString *key in dictionary) {
         id value = [dictionary objectForKey:key];
         if ([value isKindOfClass:[NSDictionary class]])
-            [mutableDictionary setObject:dictionaryRemovingNonJSONTypes(value) forKey:key];
+            [mutableDictionary setObject:dictionaryRemovingNonSupportedTypes(value) forKey:key];
         else if ([value isKindOfClass:[NSArray class]])
-            [mutableDictionary setObject:arrayRemovingNonJSONTypes(value) forKey:key];
-        else if (!([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]]))
+            [mutableDictionary setObject:arrayRemovingNonSupportedTypes(value) forKey:key];
+        else if (!isValueTypeSupported(value))
             [mutableDictionary removeObjectForKey:key];
     }
     return [mutableDictionary autorelease];
@@ -433,18 +440,8 @@ static NSDictionary *dictionaryRemovingNonJSONTypes(NSDictionary *dictionary)
 {
     if (accessibilityShouldRepostNotifications) {
         ASSERT(notificationName);
-        NSDictionary *info = nil;
-        if (userInfo) {
-            NSData *userInfoData = [NSJSONSerialization dataWithJSONObject:dictionaryRemovingNonJSONTypes(userInfo) options:(NSJSONWritingOptions)0 error:nil];
-            if (userInfoData) {
-                NSString *userInfoString = [[NSString alloc] initWithData:userInfoData encoding:NSUTF8StringEncoding];
-                if (userInfoString)
-                    info = [NSDictionary dictionaryWithObjectsAndKeys:notificationName, @"notificationName", userInfoString, @"userInfo", nil];
-                [userInfoString release];
-            }
-        }
-        if (!info)
-            info = [NSDictionary dictionaryWithObjectsAndKeys:notificationName, @"notificationName", nil];
+        userInfo = dictionaryRemovingNonSupportedTypes(userInfo);
+        NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:notificationName, @"notificationName", userInfo, @"userInfo", nil];
         [[NSNotificationCenter defaultCenter] postNotificationName:@"AXDRTNotification" object:self userInfo:info];
     }
 }
index 1a82ef9..69eb299 100644 (file)
@@ -53,7 +53,7 @@ void FrameSelection::notifyAccessibilityForSelectionChange(const AXTextStateChan
 
     if (m_selection.start().isNotNull() && m_selection.end().isNotNull()) {
         if (AXObjectCache* cache = document->existingAXObjectCache())
-            cache->postTextStateChangeNotification(m_selection.start().deprecatedNode(), intent, m_selection);
+            cache->postTextStateChangeNotification(m_selection.start(), intent, m_selection);
     }
 
 #if !PLATFORM(IOS)
index 528466a..ac3029d 100644 (file)
@@ -1,3 +1,29 @@
+2015-06-24  Doug Russell  <d_russell@apple.com>
+
+        Bug 146177 - AX: AXObjectCache should try to use an unignored accessibilityObject 
+        when posting a selection notification when on the border between two accessibilityObjects
+        https://bugs.webkit.org/show_bug.cgi?id=146177
+
+        Reviewed by Darin Adler.
+
+        Add support for mapping WebCore Accessibility types into JSValueRef types
+        so they can be used in tests.
+
+        * DumpRenderTree/mac/AccessibilityNotificationHandler.mm:
+        (webAccessibilityObjectWrapperClass):
+        (-[AccessibilityNotificationHandler startObserving]):
+        (makeValueRefForValue):
+        (makeArrayRefForArray):
+        (makeObjectRefForDictionary):
+        (-[AccessibilityNotificationHandler _notificationReceived:]):
+        * WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:
+        (webAccessibilityObjectWrapperClass):
+        (-[AccessibilityNotificationHandler startObserving]):
+        (makeValueRefForValue):
+        (makeArrayRefForArray):
+        (makeObjectRefForDictionary):
+        (-[AccessibilityNotificationHandler _notificationReceived:]):
+
 2015-06-24  Zalan Bujtas  <zalan@apple.com>
 
         Subpixel rendering: roundToDevicePixel() snaps to wrong value.
index 1be8067..4944475 100644 (file)
@@ -37,6 +37,7 @@
 #import <JavaScriptCore/JSStringRef.h>
 #import <JavaScriptCore/JSStringRefCF.h>
 #import <WebKit/WebFrame.h>
+#import <objc/runtime.h>
 #import <wtf/RetainPtr.h>
 
 @interface NSObject (WebAccessibilityObjectWrapperAdditions)
     JSValueProtect([mainFrame globalContext], m_notificationFunctionCallback);
 }
 
+static Class webAccessibilityObjectWrapperClass()
+{
+    static Class cls = objc_getClass("WebAccessibilityObjectWrapper");
+    ASSERT(cls);
+    return cls;
+}
+
 - (void)startObserving
 {
     // Once we start requesting notifications, it's on for the duration of the program.
     // This is to avoid any race conditions between tests turning this flag on and off. Instead
     // AccessibilityNotificationHandler can ignore events it doesn't care about.
-    id webAccessibilityObjectWrapperClass = NSClassFromString(@"WebAccessibilityObjectWrapper");
-    ASSERT(webAccessibilityObjectWrapperClass);
-    [webAccessibilityObjectWrapperClass accessibilitySetShouldRepostNotifications:YES];
+    [webAccessibilityObjectWrapperClass() accessibilitySetShouldRepostNotifications:YES];
     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_notificationReceived:) name:@"AXDRTNotification" object:nil];
 }
 
     [[NSNotificationCenter defaultCenter] removeObserver:self];
 }
 
+static JSValueRef makeValueRefForValue(JSContextRef context, id value)
+{
+    if ([value isKindOfClass:[NSString class]])
+        return JSValueMakeString(context, adopt([value createJSStringRef]).get());
+    if ([value isKindOfClass:[NSNumber class]]) {
+        if (!strcmp([value objCType], @encode(BOOL)))
+            return JSValueMakeBoolean(context, [value boolValue]);
+        return JSValueMakeNumber(context, [value doubleValue]);
+    }
+    if ([value isKindOfClass:webAccessibilityObjectWrapperClass()])
+        return AccessibilityUIElement::makeJSAccessibilityUIElement(context, value);
+    if ([value isKindOfClass:[NSDictionary class]])
+        return makeObjectRefForDictionary(context, value);
+    if ([value isKindOfClass:[NSArray class]])
+        return makeArrayRefForArray(context, value);
+    return nullptr;
+}
+
+static JSValueRef makeArrayRefForArray(JSContextRef context, NSArray *array)
+{
+    NSUInteger count = array.count;
+    JSValueRef arguments[count];
+
+    for (NSUInteger i = 0; i < count; i++)
+        arguments[i] = makeValueRefForValue(context, [array objectAtIndex:i]);
+
+    return JSObjectMakeArray(context, count, arguments, nullptr);
+}
+
+static JSValueRef makeObjectRefForDictionary(JSContextRef context, NSDictionary *dictionary)
+{
+    JSObjectRef object = JSObjectMake(context, nullptr, nullptr);
+
+    [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop)
+    {
+        if (JSValueRef propertyValue = makeValueRefForValue(context, obj))
+            JSObjectSetProperty(context, object, adopt([key createJSStringRef]).get(), propertyValue, kJSPropertyAttributeNone, nullptr);
+    }];
+
+    return object;
+}
+
 - (void)_notificationReceived:(NSNotification *)notification
 {
     NSString *notificationName = [[notification userInfo] objectForKey:@"notificationName"];
     if (m_platformElement && m_platformElement != [notification object])
         return;
 
-    NSString *userInfoJSONValue = [[notification userInfo] objectForKey:@"userInfo"];
+    NSDictionary *userInfo = [[notification userInfo] objectForKey:@"userInfo"];
 
-    JSRetainPtr<JSStringRef> jsNotification(Adopt, [notificationName createJSStringRef]);
-    JSValueRef notificationNameArgument = JSValueMakeString([mainFrame globalContext], jsNotification.get());
-    JSValueRef userInfoJSONValueArgument = nil;
-    if ([userInfoJSONValue length]) {
-        JSRetainPtr<JSStringRef> jsUserInfoJSONValue(Adopt, [userInfoJSONValue createJSStringRef]);
-        userInfoJSONValueArgument = JSValueMakeFromJSONString([mainFrame globalContext], jsUserInfoJSONValue.get());
-    }
+    JSValueRef notificationNameArgument = JSValueMakeString([mainFrame globalContext], adopt([notificationName createJSStringRef]).get());
+    JSValueRef userInfoArgument = makeObjectRefForDictionary([mainFrame globalContext], userInfo);
     if (m_platformElement) {
         // Listener for one element gets the notification name and userInfo.
         JSValueRef arguments[2];
         arguments[0] = notificationNameArgument;
-        arguments[1] = userInfoJSONValueArgument;
+        arguments[1] = userInfoArgument;
         JSObjectCallAsFunction([mainFrame globalContext], m_notificationFunctionCallback, 0, 2, arguments, 0);
     } else {
         // A global listener gets the element, notification name and userInfo.
         JSValueRef arguments[3];
         arguments[0] = AccessibilityUIElement::makeJSAccessibilityUIElement([mainFrame globalContext], AccessibilityUIElement([notification object]));
         arguments[1] = notificationNameArgument;
-        arguments[2] = userInfoJSONValueArgument;
+        arguments[2] = userInfoArgument;
         JSObjectCallAsFunction([mainFrame globalContext], m_notificationFunctionCallback, 0, 2, arguments, 0);
     }
 }
index f130366..01e98d5 100644 (file)
@@ -39,6 +39,7 @@
 #import <JavaScriptCore/JSStringRef.h>
 #import <JavaScriptCore/JSStringRefCF.h>
 #import <WebKit/WKBundleFrame.h>
+#import <objc/runtime.h>
 #import <wtf/RetainPtr.h>
 
 @interface NSObject (WebAccessibilityObjectWrapperAdditions)
     JSValueProtect(context, m_notificationFunctionCallback);
 }
 
+static Class webAccessibilityObjectWrapperClass()
+{
+    static Class cls = objc_getClass("WebAccessibilityObjectWrapper");
+    ASSERT(cls);
+    return cls;
+}
+
 - (void)startObserving
 {
     // Once we start requesting notifications, it's on for the duration of the program.
     // This is to avoid any race conditions between tests turning this flag on and off. Instead
     // AccessibilityNotificationHandler can ignore events it doesn't care about.
-    id webAccessibilityObjectWrapperClass = NSClassFromString(@"WebAccessibilityObjectWrapper");
-    ASSERT(webAccessibilityObjectWrapperClass);
-    [webAccessibilityObjectWrapperClass accessibilitySetShouldRepostNotifications:YES];
+    [webAccessibilityObjectWrapperClass() accessibilitySetShouldRepostNotifications:YES];
     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_notificationReceived:) name:@"AXDRTNotification" object:nil];
 }
 
     [[NSNotificationCenter defaultCenter] removeObserver:self];
 }
 
+static JSValueRef makeValueRefForValue(JSContextRef context, id value)
+{
+    if ([value isKindOfClass:[NSString class]])
+        return JSValueMakeString(context, adopt([value createJSStringRef]).get());
+    if ([value isKindOfClass:[NSNumber class]]) {
+        if (!strcmp([value objCType], @encode(BOOL)))
+            return JSValueMakeBoolean(context, [value boolValue]);
+        return JSValueMakeNumber(context, [value doubleValue]);
+    }
+    if ([value isKindOfClass:webAccessibilityObjectWrapperClass()])
+        return toJS(context, WTR::AccessibilityUIElement::create(static_cast<PlatformUIElement>(value)).get());
+    if ([value isKindOfClass:[NSDictionary class]])
+        return makeObjectRefForDictionary(context, value);
+    if ([value isKindOfClass:[NSArray class]])
+        return makeArrayRefForArray(context, value);
+    return nullptr;
+}
+
+static JSValueRef makeArrayRefForArray(JSContextRef context, NSArray *array)
+{
+    NSUInteger count = array.count;
+    JSValueRef arguments[count];
+
+    for (NSUInteger i = 0; i < count; i++)
+        arguments[i] = makeValueRefForValue(context, [array objectAtIndex:i]);
+
+    return JSObjectMakeArray(context, count, arguments, nullptr);
+}
+
+static JSValueRef makeObjectRefForDictionary(JSContextRef context, NSDictionary *dictionary)
+{
+    JSObjectRef object = JSObjectMake(context, nullptr, nullptr);
+
+    [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop)
+    {
+        if (JSValueRef propertyValue = makeValueRefForValue(context, obj))
+            JSObjectSetProperty(context, object, adopt([key createJSStringRef]).get(), propertyValue, kJSPropertyAttributeNone, nullptr);
+    }];
+
+    return object;
+}
+
 - (void)_notificationReceived:(NSNotification *)notification
 {
     NSString *notificationName = [[notification userInfo] objectForKey:@"notificationName"];
     if (m_platformElement && m_platformElement != [notification object])
         return;
 
-    NSString *userInfoJSONValue = [[notification userInfo] objectForKey:@"userInfo"];
+    NSDictionary *userInfo = [[notification userInfo] objectForKey:@"userInfo"];
 
     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(WTR::InjectedBundle::singleton().page()->page());
     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
 
-    JSRetainPtr<JSStringRef> jsNotification(Adopt, [notificationName createJSStringRef]);
-    JSValueRef notificationNameArgument = JSValueMakeString(context, jsNotification.get());
-    JSValueRef userInfoJSONValueArgument = nil;
-    if ([userInfoJSONValue length]) {
-        JSRetainPtr<JSStringRef> jsUserInfoJSONValue(Adopt, [userInfoJSONValue createJSStringRef]);
-        userInfoJSONValueArgument = JSValueMakeFromJSONString(context, jsUserInfoJSONValue.get());
-    }
+    JSValueRef notificationNameArgument = JSValueMakeString(context, adopt([notificationName createJSStringRef]).get());
+    JSValueRef userInfoArgument = makeObjectRefForDictionary(context, userInfo);
     if (m_platformElement) {
         // Listener for one element gets the notification name and userInfo.
         JSValueRef arguments[2];
         arguments[0] = notificationNameArgument;
-        arguments[1] = userInfoJSONValueArgument;
+        arguments[1] = userInfoArgument;
         JSObjectCallAsFunction(context, const_cast<JSObjectRef>(m_notificationFunctionCallback), 0, 2, arguments, 0);
     } else {
         // A global listener gets the element, notification name and userInfo.
         JSValueRef arguments[3];
-        arguments[0] = toJS(context, WTF::getPtr(WTR::AccessibilityUIElement::create([notification object])));
+        arguments[0] = toJS(context, WTR::AccessibilityUIElement::create([notification object]).get());
         arguments[1] = notificationNameArgument;
-        arguments[2] = userInfoJSONValueArgument;
-        JSObjectCallAsFunction(context, const_cast<JSObjectRef>(m_notificationFunctionCallback), 0, 2, arguments, 0);
+        arguments[2] = userInfoArgument;
+        JSObjectCallAsFunction(context, const_cast<JSObjectRef>(m_notificationFunctionCallback), 0, 3, arguments, 0);
     }
 }