1/*
2 * Copyright (C) 2008 Nuanti Ltd.
3 * Copyright (C) 2009 Jan Alonzo
4 * Copyright (C) 2009, 2010, 2012 Igalia S.L.
5 *
6 * Portions from Mozilla a11y, copyright as follows:
7 *
8 * The Original Code is mozilla.org code.
9 *
10 * The Initial Developer of the Original Code is
11 * Sun Microsystems, Inc.
12 * Portions created by the Initial Developer are Copyright (C) 2002
13 * the Initial Developer. All Rights Reserved.
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Library General Public
17 * License as published by the Free Software Foundation; either
18 * version 2 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Library General Public License for more details.
24 *
25 * You should have received a copy of the GNU Library General Public License
26 * along with this library; see the file COPYING.LIB. If not, write to
27 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 * Boston, MA 02110-1301, USA.
29 */
30
31#include "config.h"
32#include "WebKitAccessibleUtil.h"
33
34#if HAVE(ACCESSIBILITY)
35
36#include "AXObjectCache.h"
37#include "AccessibilityObject.h"
38#include "FrameView.h"
39#include "IntRect.h"
40#include "Node.h"
41#include "Range.h"
42#include "RenderObject.h"
43#include "TextIterator.h"
44#include "VisibleSelection.h"
45
46#include <wtf/text/AtomicString.h>
47#include <wtf/text/CString.h>
48
49using namespace WebCore;
50
51AtkAttributeSet* addToAtkAttributeSet(AtkAttributeSet* attributeSet, const char* name, const char* value)
52{
53 AtkAttribute* attribute = static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute)));
54 attribute->name = g_strdup(name);
55 attribute->value = g_strdup(value);
56 attributeSet = g_slist_prepend(attributeSet, attribute);
57 return attributeSet;
58}
59
60void contentsRelativeToAtkCoordinateType(AccessibilityObject* coreObject, AtkCoordType coordType, IntRect rect, gint* x, gint* y, gint* width, gint* height)
61{
62 FrameView* frameView = coreObject->documentFrameView();
63
64 if (frameView) {
65 switch (coordType) {
66 case ATK_XY_WINDOW:
67 rect = frameView->contentsToWindow(rect);
68 break;
69 case ATK_XY_SCREEN:
70 rect = frameView->contentsToScreen(rect);
71 break;
72#if ATK_CHECK_VERSION(2, 30, 0)
73 case ATK_XY_PARENT:
74 RELEASE_ASSERT_NOT_REACHED();
75#endif
76 }
77 }
78
79 if (x)
80 *x = rect.x();
81 if (y)
82 *y = rect.y();
83 if (width)
84 *width = rect.width();
85 if (height)
86 *height = rect.height();
87}
88
89// FIXME: Different kinds of elements are putting the title tag to use
90// in different AX fields. This might not be 100% correct but we will
91// keep it now in order to achieve consistency with previous behavior.
92static bool titleTagShouldBeUsedInDescriptionField(AccessibilityObject* coreObject)
93{
94 return (coreObject->isLink() && !coreObject->isImageMapLink()) || coreObject->isImage();
95}
96
97// This should be the "visible" text that's actually on the screen if possible.
98// If there's alternative text, that can override the title.
99String accessibilityTitle(AccessibilityObject* coreObject)
100{
101 Vector<AccessibilityText> textOrder;
102 coreObject->accessibilityText(textOrder);
103
104 for (const AccessibilityText& text : textOrder) {
105 // Once we encounter visible text, or the text from our children that should be used foremost.
106 if (text.textSource == AccessibilityTextSource::Visible || text.textSource == AccessibilityTextSource::Children)
107 return text.text;
108
109 // If there's an element that labels this object and it's not exposed, then we should use
110 // that text as our title.
111 if (text.textSource == AccessibilityTextSource::LabelByElement && !coreObject->exposesTitleUIElement())
112 return text.text;
113
114 // Elements of role AccessibilityRole::Toolbar will return its title as AccessibilityTextSource::Alternative.
115 if (coreObject->roleValue() == AccessibilityRole::Toolbar && text.textSource == AccessibilityTextSource::Alternative)
116 return text.text;
117
118 // FIXME: The title tag is used in certain cases for the title. This usage should
119 // probably be in the description field since it's not "visible".
120 if (text.textSource == AccessibilityTextSource::TitleTag && !titleTagShouldBeUsedInDescriptionField(coreObject))
121 return text.text;
122 }
123
124 return String();
125}
126
127String accessibilityDescription(AccessibilityObject* coreObject)
128{
129 Vector<AccessibilityText> textOrder;
130 coreObject->accessibilityText(textOrder);
131
132 bool visibleTextAvailable = false;
133 for (const AccessibilityText& text : textOrder) {
134 if (text.textSource == AccessibilityTextSource::Alternative)
135 return text.text;
136
137 switch (text.textSource) {
138 case AccessibilityTextSource::Visible:
139 case AccessibilityTextSource::Children:
140 case AccessibilityTextSource::LabelByElement:
141 visibleTextAvailable = true;
142 default:
143 break;
144 }
145
146 if (text.textSource == AccessibilityTextSource::TitleTag && !visibleTextAvailable)
147 return text.text;
148 }
149
150 return String();
151}
152
153bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& selection)
154{
155 if (!coreObject || !coreObject->isAccessibilityRenderObject())
156 return false;
157
158 if (selection.isNone())
159 return false;
160
161 RefPtr<Range> range = selection.toNormalizedRange();
162 if (!range)
163 return false;
164
165 // We want to check that both the selection intersects the node
166 // AND that the selection is not just "touching" one of the
167 // boundaries for the selected node. We want to check whether the
168 // node is actually inside the region, at least partially.
169 auto& node = *coreObject->node();
170 auto* lastDescendant = node.lastDescendant();
171 unsigned lastOffset = lastOffsetInNode(lastDescendant);
172 auto intersectsResult = range->intersectsNode(node);
173 return !intersectsResult.hasException()
174 && intersectsResult.releaseReturnValue()
175 && (&range->endContainer() != &node || range->endOffset())
176 && (&range->startContainer() != lastDescendant || range->startOffset() != lastOffset);
177}
178
179AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* referenceObject, int& offset)
180{
181 // Indication that something bogus has transpired.
182 offset = -1;
183
184 Document* document = referenceObject->document();
185 if (!document)
186 return nullptr;
187
188 Node* focusedNode = referenceObject->selection().end().containerNode();
189 if (!focusedNode)
190 return nullptr;
191
192 RenderObject* focusedRenderer = focusedNode->renderer();
193 if (!focusedRenderer)
194 return nullptr;
195
196 AccessibilityObject* focusedObject = document->axObjectCache()->getOrCreate(focusedRenderer);
197 if (!focusedObject)
198 return nullptr;
199
200 // Look for the actual (not ignoring accessibility) selected object.
201 AccessibilityObject* firstUnignoredParent = focusedObject;
202 if (firstUnignoredParent->accessibilityIsIgnored())
203 firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
204 if (!firstUnignoredParent)
205 return nullptr;
206
207 // Don't ignore links if the offset is being requested for a link
208 // or if the link is a block.
209 if (!referenceObject->isLink() && firstUnignoredParent->isLink()
210 && !(firstUnignoredParent->renderer() && !firstUnignoredParent->renderer()->isInline()))
211 firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
212 if (!firstUnignoredParent)
213 return nullptr;
214
215 // The reference object must either coincide with the focused
216 // object being considered, or be a descendant of it.
217 if (referenceObject->isDescendantOfObject(firstUnignoredParent))
218 referenceObject = firstUnignoredParent;
219
220 Node* startNode = nullptr;
221 if (firstUnignoredParent != referenceObject || firstUnignoredParent->isTextControl()) {
222 // We need to use the first child's node of the reference
223 // object as the start point to calculate the caret offset
224 // because we want it to be relative to the object of
225 // reference, not just to the focused object (which could have
226 // previous siblings which should be taken into account too).
227 AccessibilityObject* axFirstChild = referenceObject->firstChild();
228 if (axFirstChild)
229 startNode = axFirstChild->node();
230 }
231 // Getting the Position of a PseudoElement now triggers an assertion.
232 // This can occur when clicking on empty space in a render block.
233 if (!startNode || startNode->isPseudoElement())
234 startNode = firstUnignoredParent->node();
235
236 // Check if the node for the first parent object not ignoring
237 // accessibility is null again before using it. This might happen
238 // with certain kind of accessibility objects, such as the root
239 // one (the scroller containing the webArea object).
240 if (!startNode)
241 return nullptr;
242
243 VisiblePosition startPosition = VisiblePosition(positionBeforeNode(startNode), DOWNSTREAM);
244 VisiblePosition endPosition = firstUnignoredParent->selection().visibleEnd();
245
246 if (startPosition == endPosition)
247 offset = 0;
248 else if (!isStartOfLine(endPosition)) {
249 RefPtr<Range> range = makeRange(startPosition, endPosition.previous());
250 offset = TextIterator::rangeLength(range.get(), true) + 1;
251 } else {
252 RefPtr<Range> range = makeRange(startPosition, endPosition);
253 offset = TextIterator::rangeLength(range.get(), true);
254 }
255
256 return firstUnignoredParent;
257}
258
259#endif
260