From fd819b55c1103ad863d6351f1d6ad1c77fb51e1b Mon Sep 17 00:00:00 2001 From: Eitan Isaacson Date: Thu, 23 Jul 2020 21:31:35 +0000 Subject: [PATCH] Bug 1654679 - Add selected text marker range to text selection changed events. r=morgan Differential Revision: https://phabricator.services.mozilla.com/D84616 --- accessible/mac/AccessibleWrap.mm | 3 +- accessible/mac/Platform.mm | 2 +- accessible/mac/mozAccessible.mm | 17 ++- .../tests/browser/mac/browser_text_input.js | 102 ++++++++++++++---- 4 files changed, 101 insertions(+), 23 deletions(-) diff --git a/accessible/mac/AccessibleWrap.mm b/accessible/mac/AccessibleWrap.mm index 336f20f768eb..9ea6ac2094a7 100644 --- a/accessible/mac/AccessibleWrap.mm +++ b/accessible/mac/AccessibleWrap.mm @@ -166,7 +166,8 @@ nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { // If the selection is collapsed, invalidate our text selection cache. MOXTextMarkerDelegate* delegate = [MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()]; - [delegate invalidateSelection]; + int32_t caretOffset = event->GetCaretOffset(); + [delegate setSelectionFrom:eventTarget at:caretOffset to:eventTarget at:caretOffset]; } [nativeAcc handleAccessibleEvent:eventType]; diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm index 32c28cd47492..f8b634885185 100644 --- a/accessible/mac/Platform.mm +++ b/accessible/mac/Platform.mm @@ -100,7 +100,7 @@ void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset, bool aIsSele if (aIsSelectionCollapsed) { // If selection is collapsed, invalidate selection. MOXTextMarkerDelegate* delegate = [MOXTextMarkerDelegate getOrCreateForDoc:aTarget->Document()]; - [delegate invalidateSelection]; + [delegate setSelectionFrom:aTarget at:aOffset to:aTarget at:aOffset]; } if (wrapper) { diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm index 570f3537db4a..74d060b089b4 100644 --- a/accessible/mac/mozAccessible.mm +++ b/accessible/mac/mozAccessible.mm @@ -840,10 +840,21 @@ enum AXTextStateChangeType { case nsIAccessibleEvent::EVENT_SELECTION_WITHIN: [self moxPostNotification:NSAccessibilitySelectedChildrenChangedNotification]; break; - case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: - case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: - [self moxPostNotification:NSAccessibilitySelectedTextChangedNotification]; + case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { + // We consider any caret move event to be a selected text change event. + // So dispatching an event for EVENT_TEXT_SELECTION_CHANGED would be reduntant. + id delegate = [self moxTextMarkerDelegate]; + id selectedRange = [delegate moxSelectedTextMarkerRange]; + NSDictionary* userInfo = @{ + @"AXTextChangeElement": self, + @"AXSelectedTextMarkerRange": (selectedRange ? selectedRange : [NSNull null]) + }; + + mozAccessible* webArea = GetNativeFromGeckoAccessible([self geckoDocument]); + [webArea moxPostNotification:NSAccessibilitySelectedTextChangedNotification withUserInfo:userInfo]; + [self moxPostNotification:NSAccessibilitySelectedTextChangedNotification withUserInfo:userInfo]; break; + } } } diff --git a/accessible/tests/browser/mac/browser_text_input.js b/accessible/tests/browser/mac/browser_text_input.js index 0dfeedf2da62..32f5b5f22a92 100644 --- a/accessible/tests/browser/mac/browser_text_input.js +++ b/accessible/tests/browser/mac/browser_text_input.js @@ -64,7 +64,54 @@ function testValueChangedEventData( is(str, expectedWordAtLeft); } -async function synthKeyAndTestEvent( +function eventDuoPromise(eventName, expectedId) { + return [ + waitForMacEventWithInfo(eventName, (iface, data) => { + return ( + iface.getAttributeValue("AXRole") == "AXWebArea" && + !!data && + data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier") == + expectedId + ); + }), + waitForMacEventWithInfo( + eventName, + (iface, data) => + iface.getAttributeValue("AXDOMIdentifier") == expectedId && !!data + ), + ]; +} + +async function synthKeyAndTestSelectionChanged( + synthKey, + synthEvent, + expectedId, + expectedSelectionString +) { + let valueChangedEvents = Promise.all( + eventDuoPromise("AXSelectedTextChanged", expectedId) + ); + + EventUtils.synthesizeKey(synthKey, synthEvent); + let [, inputEvent] = await valueChangedEvents; + is( + inputEvent.data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier"), + expectedId, + "Correct AXTextChangeElement" + ); + + let rangeString = inputEvent.macIface.getParameterizedAttributeValue( + "AXStringForTextMarkerRange", + inputEvent.data.AXSelectedTextMarkerRange + ); + is( + rangeString, + expectedSelectionString, + `selection has correct value (${expectedSelectionString})` + ); +} + +async function synthKeyAndTestValueChanged( synthKey, synthEvent, expectedId, @@ -72,23 +119,14 @@ async function synthKeyAndTestEvent( expectedEditType, expectedWordAtLeft ) { - let valueChangedEvents = Promise.all([ - waitForMacEventWithInfo("AXValueChanged", (iface, data) => { - return ( - iface.getAttributeValue("AXRole") == "AXWebArea" && - !!data && - data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier") == "input" - ); - }), - waitForMacEventWithInfo( - "AXValueChanged", - (iface, data) => - iface.getAttributeValue("AXDOMIdentifier") == "input" && !!data - ), - ]); + let valueChangedEvents = Promise.all( + eventDuoPromise("AXSelectedTextChanged", expectedId).concat( + eventDuoPromise("AXValueChanged", expectedId) + ) + ); EventUtils.synthesizeKey(synthKey, synthEvent); - let [webareaEvent, inputEvent] = await valueChangedEvents; + let [, , webareaEvent, inputEvent] = await valueChangedEvents; testValueChangedEventData( webareaEvent.macIface, @@ -127,7 +165,7 @@ addAccessibleTask( expectedChangeValue, expectedWordAtLeft ) { - await synthKeyAndTestEvent( + await synthKeyAndTestValueChanged( synthKey, null, "input", @@ -153,7 +191,7 @@ addAccessibleTask( await testTextInput("d", "d", "world"); async function testTextDelete(expectedChangeValue, expectedWordAtLeft) { - await synthKeyAndTestEvent( + await synthKeyAndTestValueChanged( "KEY_Backspace", null, "input", @@ -165,5 +203,33 @@ addAccessibleTask( await testTextDelete("d", "worl"); await testTextDelete("l", "wor"); + + await synthKeyAndTestSelectionChanged("KEY_ArrowLeft", null, "input", ""); + await synthKeyAndTestSelectionChanged( + "KEY_ArrowLeft", + { shiftKey: true }, + "input", + "o" + ); + await synthKeyAndTestSelectionChanged( + "KEY_ArrowLeft", + { shiftKey: true }, + "input", + "wo" + ); + await synthKeyAndTestSelectionChanged("KEY_ArrowLeft", null, "input", ""); + await synthKeyAndTestSelectionChanged( + "KEY_Home", + { shiftKey: true }, + "input", + "hello " + ); + await synthKeyAndTestSelectionChanged("KEY_ArrowLeft", null, "input", ""); + await synthKeyAndTestSelectionChanged( + "KEY_ArrowRight", + { shiftKey: true, altKey: true }, + "input", + "hello" + ); } );