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 | |
49 | using namespace WebCore; |
50 | |
51 | AtkAttributeSet* 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 | |
60 | void 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. |
92 | static 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. |
99 | String 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 | |
127 | String 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 | |
153 | bool 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 | |
179 | AccessibilityObject* 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 | |