commit 0a130f775763457e4b30645c9fba7366a42e5620 parent 1757b53dcc61de7391e82e48a472e2bed4f3d9b5 Author: Sandor Molnar <smolnar@mozilla.com> Date: Tue, 21 Oct 2025 21:14:08 +0300 Revert "Bug 1942799 - P6: Change mozTextAccessible to a mozAccessible category. r=Jamie" for causing build bustages @ Platform.mm This reverts commit c06c2d185f7043763f3025d700a1dc412acba95c. Revert "Bug 1942799 - P5: Remove private mozTextAccessible extension. r=Jamie" This reverts commit 0e6e2e8f09af3f0a12f09d083a77ed8f1c1811fa. Revert "Bug 1942799 - P4: Remove specialized moxRole and provide AXTextField/AXTextArea role to contenteditable. r=Jamie" This reverts commit b6bfacef41f1e4d88558734344b1ecc747cc5c5d. Revert "Bug 1942799 - P3: Remove specialized moxRequired, moxInvalid and moxValue. r=morgan" This reverts commit 34472dcf24e9af98c5e1e89629eca4f64014949b. Revert "Bug 1942799 - P2: Make root editables multiline by default and expose value. r=morgan" This reverts commit c1c636ea74632040337fff8114dc2bd0b9624f20. Revert "Bug 1942799 - P1: Intro IsEditable(Root) to distinguish editable content. r=morgan" This reverts commit 35f6ad218775c11df3ed619a58ca5d92368b460f. Diffstat:
23 files changed, 223 insertions(+), 316 deletions(-)
diff --git a/accessible/base/ARIAStateMap.cpp b/accessible/base/ARIAStateMap.cpp @@ -30,9 +30,6 @@ struct EnumTypeData { // States to clear in case of match. const uint64_t mClearState; - - // State if attribute is missing or value doesn't match any enum values. - const uint64_t mDefaultState; }; enum ETokenType { @@ -91,7 +88,6 @@ bool aria::MapToState(EStateRule aRule, dom::Element* aElement, {states::SUPPORTS_AUTOCOMPLETION, states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION, states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION}, - 0, 0}; MapEnumType(aElement, aState, data); @@ -103,7 +99,6 @@ bool aria::MapToState(EStateRule aRule, dom::Element* aElement, nsGkAtoms::aria_busy, {nsGkAtoms::_true, nsGkAtoms::error, nullptr}, {states::BUSY, states::INVALID}, - 0, 0}; MapEnumType(aElement, aState, data); @@ -185,26 +180,11 @@ bool aria::MapToState(EStateRule aRule, dom::Element* aElement, } case eARIAMultiline: { - static const EnumTypeData data = { - nsGkAtoms::aria_multiline, - {nsGkAtoms::_true, nsGkAtoms::_false, nullptr}, - {states::MULTI_LINE, states::SINGLE_LINE}, - states::MULTI_LINE | states::SINGLE_LINE, - states::SINGLE_LINE}; - - MapEnumType(aElement, aState, data); - return true; - } - - case eARIAMultilineByDefault: { - static const EnumTypeData data = { - nsGkAtoms::aria_multiline, - {nsGkAtoms::_true, nsGkAtoms::_false, nullptr}, - {states::MULTI_LINE, states::SINGLE_LINE}, - states::MULTI_LINE | states::SINGLE_LINE, - states::MULTI_LINE}; + static const TokenTypeData data(nsGkAtoms::aria_multiline, + eBoolType | eDefinedIfAbsent, 0, + states::MULTI_LINE, states::SINGLE_LINE); - MapEnumType(aElement, aState, data); + MapTokenType(aElement, aState, data); return true; } @@ -222,8 +202,7 @@ bool aria::MapToState(EStateRule aRule, dom::Element* aElement, nsGkAtoms::aria_orientation, {nsGkAtoms::horizontal, nsGkAtoms::vertical, nullptr}, {states::HORIZONTAL, states::VERTICAL}, - states::HORIZONTAL | states::VERTICAL, - 0}; + states::HORIZONTAL | states::VERTICAL}; MapEnumType(aElement, aState, data); return true; @@ -323,11 +302,6 @@ static void MapEnumType(dom::Element* aElement, uint64_t* aState, case 2: *aState = (*aState & ~aData.mClearState) | aData.mStates[2]; return; - default: - if (aData.mDefaultState) { - *aState = (*aState & ~aData.mClearState) | aData.mDefaultState; - } - return; } } diff --git a/accessible/base/ARIAStateMap.h b/accessible/base/ARIAStateMap.h @@ -35,7 +35,6 @@ enum EStateRule { eARIAInvalid, eARIAModal, eARIAMultiline, - eARIAMultilineByDefault, eARIAMultiSelectable, eARIAOrientation, eARIAPressed, diff --git a/accessible/basetypes/Accessible.cpp b/accessible/basetypes/Accessible.cpp @@ -199,35 +199,6 @@ bool Accessible::IsTextRole() { return true; } -bool Accessible::IsEditableRoot() const { - if (IsTextField()) { - // A text field is always an editable root. - return true; - } - - const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); - if (roleMapEntry && (roleMapEntry->role == roles::ENTRY || - roleMapEntry->role == roles::SEARCHBOX)) { - // An aria text field is always an editable root. - return true; - } - - if (!IsEditable()) { - return false; - } - - if (IsDoc()) { - return true; - } - - Accessible* parent = Parent(); - if (parent && !parent->IsEditable()) { - return true; - } - - return false; -} - uint32_t Accessible::StartOffset() { MOZ_ASSERT(IsLink(), "StartOffset is called not on hyper link!"); Accessible* parent = Parent(); diff --git a/accessible/basetypes/Accessible.h b/accessible/basetypes/Accessible.h @@ -623,10 +623,6 @@ class Accessible { */ bool IsTextRole(); - virtual bool IsEditable() const = 0; - - bool IsEditableRoot() const; - bool IsGenericHyperText() const { return mType == eHyperTextType; } bool IsHTMLBr() const { return mType == eHTMLBRType; } diff --git a/accessible/generic/HyperTextAccessible.cpp b/accessible/generic/HyperTextAccessible.cpp @@ -85,6 +85,13 @@ uint64_t HyperTextAccessible::NativeState() const { return states; } +bool HyperTextAccessible::IsEditable() const { + if (!mContent) { + return false; + } + return mContent->AsElement()->State().HasState(dom::ElementState::READWRITE); +} + uint32_t HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset, bool aIsEndOffset) const { diff --git a/accessible/generic/HyperTextAccessible.h b/accessible/generic/HyperTextAccessible.h @@ -59,6 +59,11 @@ class HyperTextAccessible : public AccessibleWrap, LocalAccessible* aChild) override; virtual Relation RelationByType(RelationType aType) const override; + /** + * Return whether the associated content is editable. + */ + bool IsEditable() const; + // HyperTextAccessible (static helper method) // Convert content offset to rendered text offset diff --git a/accessible/generic/LocalAccessible.cpp b/accessible/generic/LocalAccessible.cpp @@ -1706,12 +1706,6 @@ void LocalAccessible::ApplyARIAState(uint64_t* aState) const { aria::MapToState(aria::eARIAPressed, element, aState); } - if (!IsTextField() && IsEditableRoot()) { - // HTML text fields will have their own multi/single line calcuation in - // NativeState. - aria::MapToState(aria::eARIAMultilineByDefault, element, aState); - } - if (!roleMapEntry) return; *aState |= roleMapEntry->state; @@ -1761,16 +1755,18 @@ void LocalAccessible::Value(nsString& aValue) const { } const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (!roleMapEntry) { + return; + } // Value of textbox is a textified subtree. - if ((roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) || - (IsGeneric() && IsEditableRoot())) { + if (roleMapEntry->Is(nsGkAtoms::textbox)) { nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); return; } // Value of combobox is a text of current or selected item. - if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) { + if (roleMapEntry->Is(nsGkAtoms::combobox)) { LocalAccessible* option = CurrentItem(); if (!option) { uint32_t childCount = ChildCount(); @@ -2538,11 +2534,6 @@ bool LocalAccessible::IsPopover() const { return el && el->IsHTMLElement() && el->HasAttr(nsGkAtoms::popover); } -bool LocalAccessible::IsEditable() const { - dom::Element* el = Elm(); - return el && el->State().HasState(dom::ElementState::READWRITE); -} - void LocalAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset, uint32_t aLength) { // Return text representation of non-text accessible within hypertext diff --git a/accessible/generic/LocalAccessible.h b/accessible/generic/LocalAccessible.h @@ -429,8 +429,6 @@ class LocalAccessible : public nsISupports, public Accessible { virtual bool IsPopover() const override; - virtual bool IsEditable() const override; - /** * Get a pointer to accessibility interface for this node, which is specific * to the OS/accessibility toolkit we're running on. diff --git a/accessible/ipc/RemoteAccessible.cpp b/accessible/ipc/RemoteAccessible.cpp @@ -302,8 +302,7 @@ void RemoteAccessible::Value(nsString& aValue) const { const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); // Value of textbox is a textified subtree. - if ((roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) || - (IsGeneric() && IsEditableRoot())) { + if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) { nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); return; } @@ -1562,22 +1561,6 @@ bool RemoteAccessible::IsPopover() const { return mCachedFields && mCachedFields->HasAttribute(CacheKey::PopupType); } -bool RemoteAccessible::IsEditable() const { - if (RequestDomainsIfInactive(CacheDomain::State)) { - return false; - } - - if (mCachedFields) { - if (auto rawState = - mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) { - VERIFY_CACHE(CacheDomain::State); - return (*rawState & states::EDITABLE) != 0; - } - } - - return false; -} - #if !defined(XP_WIN) void RemoteAccessible::Announce(const nsString& aAnnouncement, uint16_t aPriority) { diff --git a/accessible/ipc/RemoteAccessible.h b/accessible/ipc/RemoteAccessible.h @@ -392,8 +392,6 @@ class RemoteAccessible : public Accessible, public HyperTextAccessibleBase { virtual bool HasPrimaryAction() const override; - virtual bool IsEditable() const override; - #if !defined(XP_WIN) void Announce(const nsString& aAnnouncement, uint16_t aPriority); #endif // !defined(XP_WIN) diff --git a/accessible/mac/AccessibleWrap.mm b/accessible/mac/AccessibleWrap.mm @@ -100,6 +100,10 @@ Class AccessibleWrap::GetNativeType() { return [MOXOuterDoc class]; } + if (IsTextField() && !HasNumericValue()) { + return [mozTextAccessible class]; + } + return GetTypeFromRole(Role()); NS_OBJC_END_TRY_BLOCK_RETURN(nil); @@ -237,6 +241,10 @@ 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,9 +137,6 @@ 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,10 +543,6 @@ 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,8 +64,6 @@ // 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,6 +13,7 @@ #include "RemoteAccessible.h" #include "DocAccessibleParent.h" #include "mozTableAccessible.h" +#include "mozTextAccessible.h" #include "MOXOuterDoc.h" #include "MOXWebAreaAccessible.h" #include "nsAccUtils.h" @@ -68,6 +69,8 @@ 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()); } @@ -136,8 +139,9 @@ void PlatformCaretMoveEvent(Accessible* aTarget, int32_t aOffset, } if (wrapper) { - if (mozAccessible* editable = [wrapper moxEditableAncestor]) { - [editable + if (mozTextAccessible* textAcc = + static_cast<mozTextAccessible*>([wrapper moxEditableAncestor])) { + [textAcc handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED]; } else { [wrapper @@ -149,17 +153,16 @@ void PlatformCaretMoveEvent(Accessible* aTarget, int32_t aOffset, void PlatformTextChangeEvent(Accessible* aTarget, const nsAString& aStr, int32_t aStart, uint32_t aLen, bool aIsInsert, bool aFromUser) { - 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]; - } + 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(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,6 +88,11 @@ 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,6 +15,7 @@ #import "MOXSearchInfo.h" #import "MOXTextMarkerDelegate.h" #import "MOXWebAreaAccessible.h" +#import "mozTextAccessible.h" #import "mozRootAccessible.h" #include "LocalAccessible-inl.h" @@ -162,10 +163,6 @@ using namespace mozilla::a11y; return [self stateWithMask:states::EXPANDABLE] == 0; } - if ([self blockTextFieldMethod:selector]) { - return YES; - } - return [super moxBlockSelector:selector]; } @@ -295,17 +292,6 @@ using namespace mozilla::a11y; } - (NSString*)moxRole { - if (mRole == roles::ENTRY || - (mGeckoAccessible->IsGeneric() && mGeckoAccessible->IsEditableRoot())) { - if ([self stateWithMask:states::MULTI_LINE]) { - // This is a special case where we have a separate role when an entry is a - // multiline text area. - return NSAccessibilityTextAreaRole; - } - - return NSAccessibilityTextFieldRole; - } - #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \ msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \ nameRule) \ @@ -856,8 +842,8 @@ static bool ProvidesTitle(const Accessible* aAccessible, nsString& aName) { } - (id)moxEditableAncestor { - return [self moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) { - return [moxAcc moxIsTextField]; + return [self moxFindAncestor:^BOOL(id moxAcc, BOOL* stop) { + return [moxAcc isKindOfClass:[mozTextAccessible class]]; }]; } @@ -1078,6 +1064,13 @@ 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,12 +7,24 @@ #import "mozAccessible.h" -@interface mozAccessible (TextField) +@interface mozTextAccessible : mozAccessible + +// override +- (id)moxValue; + +// override +- (id)moxRequired; + +// override +- (NSString*)moxInvalid; // override - (NSNumber*)moxInsertionPointLineNumber; // override +- (NSString*)moxRole; + +// override - (NSNumber*)moxNumberOfCharacters; // override @@ -25,6 +37,9 @@ - (NSValue*)moxVisibleCharacterRange; // override +- (BOOL)moxBlockSelector:(SEL)selector; + +// override - (void)moxSetValue:(id)value; // override @@ -51,16 +66,17 @@ // override - (NSValue*)moxBoundsForRange:(NSValue*)range; -// override -- (BOOL)moxIsTextField; - -- (BOOL)blockTextFieldMethod:(SEL)selector; +#pragma mark - mozAccessible +// override - (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 @@ -40,35 +40,56 @@ inline NSString* ToNSString(id aValue) { return nil; } -static GeckoTextMarkerRange GetSelectionInObject(mozAccessible* aObj) { - id<MOXTextMarkerSupport> delegate = [aObj moxTextMarkerDelegate]; - GeckoTextMarkerRange selection = - [static_cast<MOXTextMarkerDelegate*>(delegate) selection]; +@interface mozTextAccessible () +- (long)textLength; +- (BOOL)isReadOnly; +- (NSString*)text; +- (GeckoTextMarkerRange)selection; +- (GeckoTextMarkerRange)textMarkerRangeFromRange:(NSValue*)range; +@end - if (!selection.IsValid() || !selection.Crop([aObj geckoAccessible])) { - // The selection is not in this accessible. Return invalid range. - return GeckoTextMarkerRange(); +@implementation mozTextAccessible + +- (id)moxValue { + // Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText + // object's AXSelectedText attribute. See bug 674612 for details. + // Also if there is no selected text, we return the full text. + // See bug 369710 for details. + if ([[self moxRole] isEqualToString:NSAccessibilityStaticTextRole]) { + NSString* selectedText = [self moxSelectedText]; + return (selectedText && [selectedText length]) ? selectedText : [self text]; } - return selection; + return [self text]; } -static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, - NSValue* aRange) { - NSRange r = [aRange rangeValue]; - Accessible* acc = [aObj geckoAccessible]; - - GeckoTextMarker startMarker = - GeckoTextMarker::MarkerFromIndex(acc, r.location); +- (id)moxRequired { + return @([self stateWithMask:states::REQUIRED] != 0); +} - GeckoTextMarker endMarker = - GeckoTextMarker::MarkerFromIndex(acc, r.location + r.length); +- (NSString*)moxInvalid { + if ([self stateWithMask:states::INVALID] != 0) { + // If the attribute exists, it has one of four values: true, false, + // grammar, or spelling. We query the attribute value here in order + // to find the correct string to return. + RefPtr<AccAttributes> attributes; + HyperTextAccessibleBase* text = mGeckoAccessible->AsHyperTextBase(); + if (text && mGeckoAccessible->IsTextRole()) { + attributes = text->DefaultTextAttributes(); + } + + nsAutoString invalidStr; + if (!attributes || + !attributes->GetAttribute(nsGkAtoms::invalid, invalidStr)) { + return @"true"; + } + return nsCocoaUtils::ToNSString(invalidStr); + } - return GeckoTextMarkerRange(startMarker, endMarker); + // If the flag is not set, we return false. + return @"false"; } -@implementation mozAccessible (TextField) - - (NSNumber*)moxInsertionPointLineNumber { MOZ_ASSERT(mGeckoAccessible); @@ -80,12 +101,20 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, return (lineNumber >= 0) ? [NSNumber numberWithInt:lineNumber] : nil; } +- (NSString*)moxRole { + if (mRole == roles::ENTRY && [self stateWithMask:states::MULTI_LINE]) { + return NSAccessibilityTextAreaRole; + } + + return [super moxRole]; +} + - (NSNumber*)moxNumberOfCharacters { - return @([[self moxValue] length]); + return @([self textLength]); } - (NSString*)moxSelectedText { - GeckoTextMarkerRange selection = GetSelectionInObject(self); + GeckoTextMarkerRange selection = [self selection]; if (!selection.IsValid()) { return nil; } @@ -94,7 +123,7 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, } - (NSValue*)moxSelectedTextRange { - GeckoTextMarkerRange selection = GetSelectionInObject(self); + GeckoTextMarkerRange selection = [self selection]; if (!selection.IsValid()) { return nil; } @@ -109,7 +138,15 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, - (NSValue*)moxVisibleCharacterRange { // XXX this won't work with Textarea and such as we actually don't give // the visible character range. - return [NSValue valueWithRange:NSMakeRange(0, [[self moxValue] length])]; + return [NSValue valueWithRange:NSMakeRange(0, [self textLength])]; +} + +- (BOOL)moxBlockSelector:(SEL)selector { + if (selector == @selector(moxSetValue:) && [self isReadOnly]) { + return YES; + } + + return [super moxBlockSelector:selector]; } - (void)moxSetValue:(id)value { @@ -145,7 +182,7 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, - (void)moxSetSelectedTextRange:(NSValue*)selectedTextRange { GeckoTextMarkerRange markerRange = - GetTextMarkerRangeFromRange(self, selectedTextRange); + [self textMarkerRangeFromRange:selectedTextRange]; if (markerRange.IsValid()) { markerRange.Select(); @@ -167,7 +204,7 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, } - (NSString*)moxStringForRange:(NSValue*)range { - GeckoTextMarkerRange markerRange = GetTextMarkerRangeFromRange(self, range); + GeckoTextMarkerRange markerRange = [self textMarkerRangeFromRange:range]; if (!markerRange.IsValid()) { return nil; @@ -177,7 +214,7 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, } - (NSAttributedString*)moxAttributedStringForRange:(NSValue*)range { - GeckoTextMarkerRange markerRange = GetTextMarkerRangeFromRange(self, range); + GeckoTextMarkerRange markerRange = [self textMarkerRangeFromRange:range]; if (!markerRange.IsValid()) { return nil; @@ -188,7 +225,7 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, - (NSValue*)moxRangeForLine:(NSNumber*)line { // XXX: actually get the integer value for the line # - return [NSValue valueWithRange:NSMakeRange(0, [[self moxValue] length])]; + return [NSValue valueWithRange:NSMakeRange(0, [self textLength])]; } - (NSNumber*)moxLineForIndex:(NSNumber*)index { @@ -197,7 +234,7 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, } - (NSValue*)moxBoundsForRange:(NSValue*)range { - GeckoTextMarkerRange markerRange = GetTextMarkerRangeFromRange(self, range); + GeckoTextMarkerRange markerRange = [self textMarkerRangeFromRange:range]; if (!markerRange.IsValid()) { return nil; @@ -206,58 +243,12 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, return markerRange.Bounds(); } -- (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; -} +#pragma mark - mozAccessible - (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, @@ -280,6 +271,63 @@ static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, [self moxPostNotification:NSAccessibilityValueChangedNotification]; } +- (void)handleAccessibleEvent:(uint32_t)eventType { + switch (eventType) { + default: + [super handleAccessibleEvent:eventType]; + break; + } +} + +#pragma mark - + +- (long)textLength { + return [[self text] length]; +} + +- (BOOL)isReadOnly { + return [self stateWithMask:states::EDITABLE] == 0; +} + +- (NSString*)text { + // A password text field returns an empty value + if (mRole == roles::PASSWORD_TEXT) { + return @""; + } + + id<MOXTextMarkerSupport> delegate = [self moxTextMarkerDelegate]; + return [delegate + moxStringForTextMarkerRange:[delegate + moxTextMarkerRangeForUIElement:self]]; +} + +- (GeckoTextMarkerRange)selection { + MOZ_ASSERT(mGeckoAccessible); + + id<MOXTextMarkerSupport> delegate = [self moxTextMarkerDelegate]; + GeckoTextMarkerRange selection = + [static_cast<MOXTextMarkerDelegate*>(delegate) selection]; + + if (!selection.IsValid() || !selection.Crop(mGeckoAccessible)) { + // The selection is not in this accessible. Return invalid range. + return GeckoTextMarkerRange(); + } + + return selection; +} + +- (GeckoTextMarkerRange)textMarkerRangeFromRange:(NSValue*)range { + NSRange r = [range rangeValue]; + + GeckoTextMarker startMarker = + GeckoTextMarker::MarkerFromIndex(mGeckoAccessible, r.location); + + GeckoTextMarker endMarker = + GeckoTextMarker::MarkerFromIndex(mGeckoAccessible, r.location + r.length); + + return GeckoTextMarkerRange(startMarker, endMarker); +} + @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, id) { +function selectedTextEventPromises(stateChangeType) { return [ waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => { return ( @@ -15,14 +15,14 @@ function selectedTextEventPromises(stateChangeType, id) { waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => { return ( info.AXTextStateChangeType == stateChangeType && - elem.getAttributeValue("AXDOMIdentifier") == id + elem.getAttributeValue("AXDOMIdentifier") == "input" ); }), ]; } -async function testInput(browser, accDoc, id = "input") { - let input = getNativeInterface(accDoc, id); +async function testInput(browser, accDoc) { + let input = getNativeInterface(accDoc, "input"); is(input.getAttributeValue("AXDescription"), "Name", "Correct input label"); is(input.getAttributeValue("AXTitle"), "", "Correct input title"); @@ -40,19 +40,19 @@ async function testInput(browser, accDoc, id = "input") { ); let evt = Promise.all([ - waitForMacEvent("AXFocusedUIElementChanged", id), - ...selectedTextEventPromises(AXTextStateChangeTypeSelectionMove, id), + waitForMacEvent("AXFocusedUIElementChanged", "input"), + ...selectedTextEventPromises(AXTextStateChangeTypeSelectionMove), ]); - await SpecialPowers.spawn(browser, [id], domId => { - content.document.getElementById(domId).focus(); + await SpecialPowers.spawn(browser, [], () => { + content.document.getElementById("input").focus(); }); await evt; evt = Promise.all( - selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend, id) + selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend) ); - await SpecialPowers.spawn(browser, [id], domId => { - let elm = content.document.getElementById(domId); + await SpecialPowers.spawn(browser, [], () => { + let elm = content.document.getElementById("input"); if (elm.setSelectionRange) { elm.setSelectionRange(6, 9); } else { @@ -86,7 +86,7 @@ async function testInput(browser, accDoc, id = "input") { ); evt = Promise.all( - selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend, id) + selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend) ); input.setAttributeValue("AXSelectedTextRange", NSRange(1, 7)); await evt; @@ -130,38 +130,6 @@ addAccessibleTask( ); /** - * contenteditable selection test with no role - */ -addAccessibleTask( - `<div aria-label="Name" id="no-role-editable" contenteditable> - <p>Elmer Fudd</p> - </div> - <div aria-label="Name" id="no-role-editable-single-line" aria-multiline="false" contenteditable> - <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"), - "AXTextArea", - "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" - ); - is( - noRoleEditableSingleLine.getAttributeValue("AXRole"), - "AXTextField", - "Correct role for single-line contenteditable with no role" - ); - } -); - -/** * contenteditable selection test */ addAccessibleTask( diff --git a/accessible/tests/browser/text/browser_editabletext.js b/accessible/tests/browser/text/browser_editabletext.js @@ -4,9 +4,6 @@ "use strict"; -/* import-globals-from ../../mochitest/states.js */ -loadScripts({ name: "states.js", dir: MOCHITESTS_DIR }); - async function testEditable(browser, acc, aBefore = "", aAfter = "") { async function resetInput() { if (acc.childCount <= 1) { @@ -262,46 +259,3 @@ addAccessibleTask( is(input.value, "aefdef", "input value correct after pasting"); } ); - -addAccessibleTask( - `<div id="editable" contenteditable="true"><p id="p">one</p></div>`, - async function testNoRoleEditable(browser, docAcc) { - const editable = findAccessibleChildByID(docAcc, "editable"); - is(editable.value, "one", "initial value correct"); - ok(true, "Set initial text"); - await invokeContentTask(browser, [], () => { - content.document.getElementById("p").firstChild.data = "two"; - }); - await untilCacheIs(() => editable.value, "two", "value changed correctly"); - - function isMultiline() { - let extState = {}; - editable.getState({}, extState); - return ( - !!(extState.value & EXT_STATE_MULTI_LINE) && - !(extState.value & EXT_STATE_SINGLE_LINE) - ); - } - - ok(isMultiline(), "Editable is in multiline state"); - await invokeSetAttribute(browser, "editable", "aria-multiline", "false"); - await untilCacheOk(() => !isMultiline(), "editable is in singleline state"); - - await invokeSetAttribute(browser, "editable", "aria-multiline"); - await untilCacheOk(() => isMultiline(), "editable is in multi-line again"); - - await invokeSetAttribute(browser, "editable", "contenteditable"); - await untilCacheOk(() => { - let extState = {}; - editable.getState({}, extState); - return ( - !(extState.value & EXT_STATE_MULTI_LINE) && - !(extState.value & EXT_STATE_SINGLE_LINE) - ); - }, "editable should have neither multi-line nor single-line state"); - }, - { - chrome: true, - topLevel: true, - } -); diff --git a/accessible/xul/XULTreeGridAccessible.cpp b/accessible/xul/XULTreeGridAccessible.cpp @@ -455,7 +455,7 @@ nsRect XULTreeGridCellAccessible::BoundsInAppUnits() const { bool XULTreeGridCellAccessible::HasPrimaryAction() const { return mColumn->Cycler() || (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX && - IsEditableCell()); + IsEditable()); } void XULTreeGridCellAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { @@ -469,7 +469,7 @@ void XULTreeGridCellAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { } if (mColumn->Type() == dom::TreeColumn_Binding::TYPE_CHECKBOX && - IsEditableCell()) { + IsEditable()) { nsAutoString value; mTreeView->GetCellValue(mRow, mColumn, value); if (value.EqualsLiteral("true")) { @@ -647,7 +647,7 @@ void XULTreeGridCellAccessible::DispatchClickEvent( //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: protected implementation -bool XULTreeGridCellAccessible::IsEditableCell() const { +bool XULTreeGridCellAccessible::IsEditable() const { // XXX: logic corresponds to tree.xml, it's preferable to have interface // method to check it. bool isEditable = false; diff --git a/accessible/xul/XULTreeGridAccessible.h b/accessible/xul/XULTreeGridAccessible.h @@ -171,10 +171,9 @@ class XULTreeGridCellAccessible : public LeafAccessible, // XULTreeGridCellAccessible /** - * Return true if value of cell can be changed. Accounting for the state of - * its column. + * Return true if value of cell can be modified. */ - bool IsEditableCell() const; + bool IsEditable() const; enum { eAction_Click = 0 };