commit 288e11d8d249b65a0a2e1d953fd6f6b8281e80eb
parent aa5d5ec9d783e86bea5cb35b336555b9072009b2
Author: Eitan Isaacson <eitan@monotonous.org>
Date: Wed, 22 Oct 2025 03:12:54 +0000
Bug 1942799 - P6: Change mozTextAccessible to a mozAccessible category. r=Jamie
Introduce a method called blockTextFieldMethod that determines whether the attributes implemented in this category should be exposed.
Make handleAccessibleTextChangeEvent exclusively implemented in the category.
Differential Revision: https://phabricator.services.mozilla.com/D268632
Diffstat:
10 files changed, 95 insertions(+), 77 deletions(-)
diff --git a/accessible/mac/AccessibleWrap.mm b/accessible/mac/AccessibleWrap.mm
@@ -100,10 +100,6 @@ Class AccessibleWrap::GetNativeType() {
return [MOXOuterDoc class];
}
- if (IsTextField() && !HasNumericValue()) {
- return [mozTextAccessible class];
- }
-
return GetTypeFromRole(Role());
NS_OBJC_END_TRY_BLOCK_RETURN(nil);
@@ -241,10 +237,6 @@ Class a11y::GetTypeFromRole(roles::Role aRole) {
case roles::PAGETABLIST:
return [mozTabGroupAccessible class];
- case roles::ENTRY:
- case roles::PASSWORD_TEXT:
- return [mozTextAccessible class];
-
case roles::TEXT_LEAF:
case roles::STATICTEXT:
return [mozTextLeafAccessible class];
diff --git a/accessible/mac/MOXAccessibleBase.h b/accessible/mac/MOXAccessibleBase.h
@@ -137,6 +137,9 @@ inline id<mozAccessible> GetObjectOrRepresentedView(id<mozAccessible> aObject) {
- (BOOL)moxIsLiveRegion;
// override
+- (BOOL)moxIsTextField;
+
+// override
- (id<MOXAccessible>)moxFindAncestor:(BOOL (^)(id<MOXAccessible> moxAcc,
BOOL* stop))findBlock;
diff --git a/accessible/mac/MOXAccessibleBase.mm b/accessible/mac/MOXAccessibleBase.mm
@@ -543,6 +543,10 @@ mozilla::LogModule* GetMacAccessibilityLog() {
return NO;
}
+- (BOOL)moxIsTextField {
+ return NO;
+}
+
#pragma mark -
// objc-style description (from NSObject); not to be confused with the
diff --git a/accessible/mac/MOXAccessibleProtocol.h b/accessible/mac/MOXAccessibleProtocol.h
@@ -64,6 +64,8 @@
// Return true if this accessible is a live region
- (BOOL)moxIsLiveRegion;
+- (BOOL)moxIsTextField;
+
// Find the nearest ancestor that returns true with the given block function
- (id<MOXAccessible> _Nullable)moxFindAncestor:
(BOOL (^_Nonnull)(id<MOXAccessible> _Nonnull moxAcc,
diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm
@@ -69,8 +69,6 @@ void ProxyCreated(RemoteAccessible* aProxy) {
type = [MOXWebAreaAccessible class];
} else if (aProxy->IsOuterDoc()) {
type = [MOXOuterDoc class];
- } else if (aProxy->IsTextField() && !aProxy->HasNumericValue()) {
- type = [mozTextAccessible class];
} else {
type = GetTypeFromRole(aProxy->Role());
}
@@ -139,9 +137,8 @@ void PlatformCaretMoveEvent(Accessible* aTarget, int32_t aOffset,
}
if (wrapper) {
- if (mozTextAccessible* textAcc =
- static_cast<mozTextAccessible*>([wrapper moxEditableAncestor])) {
- [textAcc
+ if (mozAccessible* editable = [wrapper moxEditableAncestor]) {
+ [editable
handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
} else {
[wrapper
@@ -153,16 +150,17 @@ void PlatformCaretMoveEvent(Accessible* aTarget, int32_t aOffset,
void PlatformTextChangeEvent(Accessible* aTarget, const nsAString& aStr,
int32_t aStart, uint32_t aLen, bool aIsInsert,
bool aFromUser) {
- Accessible* acc = aTarget;
- // If there is a text input ancestor, use it as the event source.
- while (acc && GetTypeFromRole(acc->Role()) != [mozTextAccessible class]) {
- acc = acc->Parent();
+ mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
+ if (wrapper) {
+ if (mozAccessible* editable = [wrapper moxEditableAncestor]) {
+ [editable handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString(aStr)
+ inserted:aIsInsert
+ inContainer:aTarget
+ at:aStart];
+ } else {
+ [wrapper maybePostValidationErrorChanged];
+ }
}
- mozAccessible* wrapper = GetNativeFromGeckoAccessible(acc ? acc : aTarget);
- [wrapper handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString(aStr)
- inserted:aIsInsert
- inContainer:aTarget
- at:aStart];
}
void PlatformShowHideEvent(Accessible*, Accessible*, bool, bool) {}
diff --git a/accessible/mac/mozAccessible.h b/accessible/mac/mozAccessible.h
@@ -88,11 +88,6 @@ enum CheckedState {
// filtered out in Platform::PlatformEvent or AccessibleWrap::HandleAccEvent!
- (void)handleAccessibleEvent:(uint32_t)eventType;
-- (void)handleAccessibleTextChangeEvent:(NSString*)change
- inserted:(BOOL)isInserted
- inContainer:(mozilla::a11y::Accessible*)container
- at:(int32_t)start;
-
- (void)maybePostValidationErrorChanged;
// internal method to retrieve a child at a given index.
diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm
@@ -15,8 +15,8 @@
#import "MOXSearchInfo.h"
#import "MOXTextMarkerDelegate.h"
#import "MOXWebAreaAccessible.h"
-#import "mozTextAccessible.h"
#import "mozRootAccessible.h"
+#import "mozTextAccessible.h"
#include "LocalAccessible-inl.h"
#include "nsAccUtils.h"
@@ -163,6 +163,10 @@ using namespace mozilla::a11y;
return [self stateWithMask:states::EXPANDABLE] == 0;
}
+ if ([self blockTextFieldMethod:selector]) {
+ return YES;
+ }
+
return [super moxBlockSelector:selector];
}
@@ -853,8 +857,8 @@ static bool ProvidesTitle(const Accessible* aAccessible, nsString& aName) {
}
- (id)moxEditableAncestor {
- return [self moxFindAncestor:^BOOL(id moxAcc, BOOL* stop) {
- return [moxAcc isKindOfClass:[mozTextAccessible class]];
+ return [self moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
+ return [moxAcc moxIsTextField];
}];
}
@@ -1075,13 +1079,6 @@ static bool ProvidesTitle(const Accessible* aAccessible, nsString& aName) {
return relations;
}
-- (void)handleAccessibleTextChangeEvent:(NSString*)change
- inserted:(BOOL)isInserted
- inContainer:(Accessible*)container
- at:(int32_t)start {
- [self maybePostValidationErrorChanged];
-}
-
- (void)handleAccessibleEvent:(uint32_t)eventType {
switch (eventType) {
case nsIAccessibleEvent::EVENT_ALERT:
diff --git a/accessible/mac/mozTextAccessible.h b/accessible/mac/mozTextAccessible.h
@@ -7,7 +7,7 @@
#import "mozAccessible.h"
-@interface mozTextAccessible : mozAccessible
+@interface mozAccessible (TextField)
// override
- (NSNumber*)moxInsertionPointLineNumber;
@@ -25,9 +25,6 @@
- (NSValue*)moxVisibleCharacterRange;
// override
-- (BOOL)moxBlockSelector:(SEL)selector;
-
-// override
- (void)moxSetValue:(id)value;
// override
@@ -54,17 +51,16 @@
// override
- (NSValue*)moxBoundsForRange:(NSValue*)range;
-#pragma mark - mozAccessible
-
// override
+- (BOOL)moxIsTextField;
+
+- (BOOL)blockTextFieldMethod:(SEL)selector;
+
- (void)handleAccessibleTextChangeEvent:(NSString*)change
inserted:(BOOL)isInserted
inContainer:(mozilla::a11y::Accessible*)container
at:(int32_t)start;
-// override
-- (void)handleAccessibleEvent:(uint32_t)eventType;
-
@end
@interface mozTextLeafAccessible : mozAccessible
diff --git a/accessible/mac/mozTextAccessible.mm b/accessible/mac/mozTextAccessible.mm
@@ -67,7 +67,7 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj,
return GeckoTextMarkerRange(startMarker, endMarker);
}
-@implementation mozTextAccessible
+@implementation mozAccessible (TextField)
- (NSNumber*)moxInsertionPointLineNumber {
MOZ_ASSERT(mGeckoAccessible);
@@ -112,15 +112,6 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj,
return [NSValue valueWithRange:NSMakeRange(0, [[self moxValue] length])];
}
-- (BOOL)moxBlockSelector:(SEL)selector {
- if (selector == @selector(moxSetValue:) &&
- [self stateWithMask:states::EDITABLE] == 0) {
- return YES;
- }
-
- return [super moxBlockSelector:selector];
-}
-
- (void)moxSetValue:(id)value {
MOZ_ASSERT(mGeckoAccessible);
@@ -215,12 +206,58 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj,
return markerRange.Bounds();
}
-#pragma mark - mozAccessible
+- (BOOL)moxIsTextField {
+ return !mGeckoAccessible->HasNumericValue() &&
+ mGeckoAccessible->IsEditableRoot();
+}
+
+- (BOOL)blockTextFieldMethod:(SEL)selector {
+ // These are the editable text methods defined in this category.
+ // We want to block them in certain cases.
+ if (selector != @selector(moxNumberOfCharacters) &&
+ selector != @selector(moxInsertionPointLineNumber) &&
+ selector != @selector(moxSelectedText) &&
+ selector != @selector(moxSelectedTextRange) &&
+ selector != @selector(moxVisibleCharacterRange) &&
+ selector != @selector(moxSetSelectedText:) &&
+ selector != @selector(moxSetSelectedTextRange:) &&
+ selector != @selector(moxSetVisibleCharacterRange:) &&
+ selector != @selector(moxStringForRange:) &&
+ selector != @selector(moxAttributedStringForRange:) &&
+ selector != @selector(moxRangeForLine:) &&
+ selector != @selector(moxLineForIndex:) &&
+ selector != @selector(moxBoundsForRange:) &&
+ selector != @selector(moxSetValue:)) {
+ return NO;
+ }
+
+ if ([[mozAccessible class] instanceMethodForSelector:selector] !=
+ [self methodForSelector:selector]) {
+ // This method was overridden by a subclass, so let it through.
+ return NO;
+ }
+
+ if (![self moxIsTextField]) {
+ // This is not an editable root, so block these methods.
+ return YES;
+ }
+
+ if (selector == @selector(moxSetValue:) &&
+ [self stateWithMask:states::EDITABLE] == 0) {
+ // The editable is read-only, so block setValue:
+ // Bug 1995330 - should rely on READONLY/UNAVAILABLE here.
+ return YES;
+ }
+
+ // Let these methods through.
+ return NO;
+}
- (void)handleAccessibleTextChangeEvent:(NSString*)change
inserted:(BOOL)isInserted
inContainer:(Accessible*)container
at:(int32_t)start {
+ MOZ_ASSERT([self moxIsTextField]);
GeckoTextMarker startMarker(container, start);
NSDictionary* userInfo = @{
@"AXTextChangeElement" : self,
@@ -243,14 +280,6 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj,
[self moxPostNotification:NSAccessibilityValueChangedNotification];
}
-- (void)handleAccessibleEvent:(uint32_t)eventType {
- switch (eventType) {
- default:
- [super handleAccessibleEvent:eventType];
- break;
- }
-}
-
@end
@implementation mozTextLeafAccessible
diff --git a/accessible/tests/browser/mac/browser_input.js b/accessible/tests/browser/mac/browser_input.js
@@ -4,7 +4,7 @@
"use strict";
-function selectedTextEventPromises(stateChangeType) {
+function selectedTextEventPromises(stateChangeType, id) {
return [
waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
return (
@@ -15,14 +15,14 @@ function selectedTextEventPromises(stateChangeType) {
waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
return (
info.AXTextStateChangeType == stateChangeType &&
- elem.getAttributeValue("AXDOMIdentifier") == "input"
+ elem.getAttributeValue("AXDOMIdentifier") == id
);
}),
];
}
-async function testInput(browser, accDoc) {
- let input = getNativeInterface(accDoc, "input");
+async function testInput(browser, accDoc, id = "input") {
+ let input = getNativeInterface(accDoc, id);
is(input.getAttributeValue("AXDescription"), "Name", "Correct input label");
is(input.getAttributeValue("AXTitle"), "", "Correct input title");
@@ -40,19 +40,19 @@ async function testInput(browser, accDoc) {
);
let evt = Promise.all([
- waitForMacEvent("AXFocusedUIElementChanged", "input"),
- ...selectedTextEventPromises(AXTextStateChangeTypeSelectionMove),
+ waitForMacEvent("AXFocusedUIElementChanged", id),
+ ...selectedTextEventPromises(AXTextStateChangeTypeSelectionMove, id),
]);
- await SpecialPowers.spawn(browser, [], () => {
- content.document.getElementById("input").focus();
+ await SpecialPowers.spawn(browser, [id], domId => {
+ content.document.getElementById(domId).focus();
});
await evt;
evt = Promise.all(
- selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend)
+ selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend, id)
);
- await SpecialPowers.spawn(browser, [], () => {
- let elm = content.document.getElementById("input");
+ await SpecialPowers.spawn(browser, [id], domId => {
+ let elm = content.document.getElementById(domId);
if (elm.setSelectionRange) {
elm.setSelectionRange(6, 9);
} else {
@@ -86,7 +86,7 @@ async function testInput(browser, accDoc) {
);
evt = Promise.all(
- selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend)
+ selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend, id)
);
input.setAttributeValue("AXSelectedTextRange", NSRange(1, 7));
await evt;
@@ -140,6 +140,7 @@ addAccessibleTask(
<p>Elmer Fudd</p>
</div>`,
async (browser, accDoc) => {
+ await testInput(browser, accDoc, "no-role-editable");
const noRoleEditable = getNativeInterface(accDoc, "no-role-editable");
is(
noRoleEditable.getAttributeValue("AXRole"),
@@ -147,6 +148,7 @@ addAccessibleTask(
"Correct role for multi-line contenteditable with no role"
);
+ await testInput(browser, accDoc, "no-role-editable-single-line");
const noRoleEditableSingleLine = getNativeInterface(
accDoc,
"no-role-editable-single-line"