blob: 7fec5e347db93439581620f49c11f0ee4d203a24 [file] [log] [blame]
/*
* 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<NSString *> *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<AccessibilityText> 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<AccessibilityText> 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<AccessibilityText> 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)