tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit c06c2d185f7043763f3025d700a1dc412acba95c
parent 0e6e2e8f09af3f0a12f09d083a77ed8f1c1811fa
Author: Eitan Isaacson <eitan@monotonous.org>
Date:   Tue, 21 Oct 2025 16:58:07 +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:
Maccessible/mac/AccessibleWrap.mm | 8--------
Maccessible/mac/MOXAccessibleBase.h | 3+++
Maccessible/mac/MOXAccessibleBase.mm | 4++++
Maccessible/mac/MOXAccessibleProtocol.h | 2++
Maccessible/mac/Platform.mm | 27++++++++++++---------------
Maccessible/mac/mozAccessible.h | 5-----
Maccessible/mac/mozAccessible.mm | 16++++++----------
Maccessible/mac/mozTextAccessible.h | 14+++++---------
Maccessible/mac/mozTextAccessible.mm | 67++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Maccessible/tests/browser/mac/browser_input.js | 26++++++++++++++------------
10 files changed, 94 insertions(+), 78 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 @@ -13,7 +13,6 @@ #include "RemoteAccessible.h" #include "DocAccessibleParent.h" #include "mozTableAccessible.h" -#include "mozTextAccessible.h" #include "MOXOuterDoc.h" #include "MOXWebAreaAccessible.h" #include "nsAccUtils.h" @@ -69,8 +68,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 +136,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 +149,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,7 +15,6 @@ #import "MOXSearchInfo.h" #import "MOXTextMarkerDelegate.h" #import "MOXWebAreaAccessible.h" -#import "mozTextAccessible.h" #import "mozRootAccessible.h" #include "LocalAccessible-inl.h" @@ -163,6 +162,10 @@ using namespace mozilla::a11y; return [self stateWithMask:states::EXPANDABLE] == 0; } + if ([self blockTextFieldMethod:selector]) { + return YES; + } + return [super moxBlockSelector:selector]; } @@ -853,8 +856,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 +1078,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"