/* * Copyright (C) 2019 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "AccessibilityObject.h" #if ENABLE(ACCESSIBILITY) && PLATFORM(COCOA) #import "WebAccessibilityObjectWrapperBase.h" namespace WebCore { PlainTextRange::PlainTextRange(NSRange r) : start(r.location) , length(r.length) { } String AccessibilityObject::speechHintAttributeValue() const { auto speak = speakAsProperty(); NSMutableArray *hints = [NSMutableArray array]; [hints addObject:(speak & SpeakAs::SpellOut) ? @"spell-out" : @"normal"]; if (speak & SpeakAs::Digits) [hints addObject:@"digits"]; if (speak & SpeakAs::LiteralPunctuation) [hints addObject:@"literal-punctuation"]; if (speak & SpeakAs::NoPunctuation) [hints addObject:@"no-punctuation"]; return [hints componentsJoinedByString:@" "]; } static bool isVisibleText(AccessibilityTextSource textSource) { switch (textSource) { case AccessibilityTextSource::Visible: case AccessibilityTextSource::Children: case AccessibilityTextSource::LabelByElement: return true; case AccessibilityTextSource::Alternative: case AccessibilityTextSource::Summary: case AccessibilityTextSource::Help: case AccessibilityTextSource::TitleTag: case AccessibilityTextSource::Placeholder: case AccessibilityTextSource::Title: case AccessibilityTextSource::Subtitle: case AccessibilityTextSource::Action: return false; } } static bool isDescriptiveText(AccessibilityTextSource textSource) { switch (textSource) { case AccessibilityTextSource::Alternative: case AccessibilityTextSource::Visible: case AccessibilityTextSource::Children: case AccessibilityTextSource::LabelByElement: return true; case AccessibilityTextSource::Summary: case AccessibilityTextSource::Help: case AccessibilityTextSource::TitleTag: case AccessibilityTextSource::Placeholder: case AccessibilityTextSource::Title: case AccessibilityTextSource::Subtitle: case AccessibilityTextSource::Action: return false; } } String AccessibilityObject::descriptionAttributeValue() const { // Static text objects should not have a description. Its content is communicated in its AXValue. // One exception is the media control labels that have a value and a description. Those are set programatically. if (roleValue() == AccessibilityRole::StaticText && !isMediaControlLabel()) return { }; Vector textOrder; accessibilityText(textOrder); // Determine if any visible text is available, which influences our usage of title tag. bool visibleTextAvailable = false; for (const auto& text : textOrder) { if (isVisibleText(text.textSource)) { visibleTextAvailable = true; break; } } NSMutableString *returnText = [NSMutableString string]; for (const auto& text : textOrder) { if (text.textSource == AccessibilityTextSource::Alternative) { [returnText appendString:text.text]; break; } switch (text.textSource) { // These are sub-components of one element (Attachment) that are re-combined in OSX and iOS. case AccessibilityTextSource::Title: case AccessibilityTextSource::Subtitle: case AccessibilityTextSource::Action: { if (!text.text.length()) break; if ([returnText length]) [returnText appendString:@", "]; [returnText appendString:text.text]; break; } default: break; } if (text.textSource == AccessibilityTextSource::TitleTag && !visibleTextAvailable) { [returnText appendString:text.text]; break; } } return returnText; } String AccessibilityObject::titleAttributeValue() const { // Static text objects should not have a title. Its content is communicated in its AXValue. if (roleValue() == AccessibilityRole::StaticText) return String(); // Meter elements should communicate their content via AXValueDescription. if (isMeter()) return { }; // Summary element should use its text node as AXTitle. if (isSummary()) return textUnderElement(); // A file upload button presents a challenge because it has button text and a value, but the // API doesn't support this paradigm. // The compromise is to return the button type in the role description and the value of the file path in the title if (isFileUploadButton() && fileUploadButtonReturnsValueInTitle()) return stringValue(); Vector textOrder; accessibilityText(textOrder); for (const auto& text : textOrder) { // If we have alternative text, then we should not expose a title. if (text.textSource == AccessibilityTextSource::Alternative) break; // Once we encounter visible text, or the text from our children that should be used foremost. if (text.textSource == AccessibilityTextSource::Visible || text.textSource == AccessibilityTextSource::Children) return text.text; // If there's an element that labels this object and it's not exposed, then we should use // that text as our title. if (text.textSource == AccessibilityTextSource::LabelByElement && !exposesTitleUIElement()) return text.text; } return { }; } String AccessibilityObject::helpTextAttributeValue() const { Vector textOrder; accessibilityText(textOrder); // Determine if any descriptive text is available, which influences our usage of title tag. bool descriptiveTextAvailable = false; for (const auto& text : textOrder) { if (isDescriptiveText(text.textSource)) { descriptiveTextAvailable = true; break; } } for (const auto& text : textOrder) { if (text.textSource == AccessibilityTextSource::Help || text.textSource == AccessibilityTextSource::Summary) return text.text; // If an element does NOT have other descriptive text the title tag should be used as its descriptive text. // But, if those ARE available, then the title tag should be used for help text instead. if (text.textSource == AccessibilityTextSource::TitleTag && descriptiveTextAvailable) return text.text; } return { }; } }; // namespace WebCore #endif // ENABLE(ACCESSIBILITY) && PLATFORM(COCOA)