1/*
2 * Copyright (C) 2016 Igalia, S.L.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29
30#if ENABLE(MATHML)
31#include "AccessibilityMathMLElement.h"
32
33#include "AXObjectCache.h"
34#include "MathMLNames.h"
35
36namespace WebCore {
37
38AccessibilityMathMLElement::AccessibilityMathMLElement(RenderObject* renderer, bool isAnonymousOperator)
39 : AccessibilityRenderObject(renderer)
40 , m_isAnonymousOperator(isAnonymousOperator)
41{
42}
43
44AccessibilityMathMLElement::~AccessibilityMathMLElement() = default;
45
46Ref<AccessibilityMathMLElement> AccessibilityMathMLElement::create(RenderObject* renderer, bool isAnonymousOperator)
47{
48 return adoptRef(*new AccessibilityMathMLElement(renderer, isAnonymousOperator));
49}
50
51AccessibilityRole AccessibilityMathMLElement::determineAccessibilityRole()
52{
53 if (!m_renderer)
54 return AccessibilityRole::Unknown;
55
56 if ((m_ariaRole = determineAriaRoleAttribute()) != AccessibilityRole::Unknown)
57 return m_ariaRole;
58
59 Node* node = m_renderer->node();
60 if (node && node->hasTagName(MathMLNames::mathTag))
61 return AccessibilityRole::DocumentMath;
62
63 // It's not clear which role a platform should choose for a math element.
64 // Declaring a math element role should give flexibility to platforms to choose.
65 return AccessibilityRole::MathElement;
66}
67
68String AccessibilityMathMLElement::textUnderElement(AccessibilityTextUnderElementMode mode) const
69{
70 if (m_isAnonymousOperator) {
71 UChar operatorChar = downcast<RenderMathMLOperator>(*m_renderer).textContent();
72 return operatorChar ? String(&operatorChar, 1) : String();
73 }
74
75 return AccessibilityRenderObject::textUnderElement(mode);
76}
77
78String AccessibilityMathMLElement::stringValue() const
79{
80 if (m_isAnonymousOperator)
81 return textUnderElement();
82
83 return AccessibilityRenderObject::stringValue();
84}
85
86bool AccessibilityMathMLElement::isIgnoredElementWithinMathTree() const
87{
88 if (m_isAnonymousOperator)
89 return false;
90
91 // Only math elements that we explicitly recognize should be included
92 // We don't want things like <mstyle> to appear in the tree.
93 if (isMathFraction() || isMathFenced() || isMathSubscriptSuperscript() || isMathRow()
94 || isMathUnderOver() || isMathRoot() || isMathText() || isMathNumber()
95 || isMathOperator() || isMathFenceOperator() || isMathSeparatorOperator()
96 || isMathIdentifier() || isMathTable() || isMathTableRow() || isMathTableCell() || isMathMultiscript())
97 return false;
98
99 return true;
100}
101
102bool AccessibilityMathMLElement::isMathFraction() const
103{
104 return m_renderer && m_renderer->isRenderMathMLFraction();
105}
106
107bool AccessibilityMathMLElement::isMathFenced() const
108{
109 return m_renderer && m_renderer->isRenderMathMLFenced();
110}
111
112bool AccessibilityMathMLElement::isMathSubscriptSuperscript() const
113{
114 return m_renderer && m_renderer->isRenderMathMLScripts() && !isMathMultiscript();
115}
116
117bool AccessibilityMathMLElement::isMathRow() const
118{
119 return m_renderer && m_renderer->isRenderMathMLRow() && !isMathRoot();
120}
121
122bool AccessibilityMathMLElement::isMathUnderOver() const
123{
124 return m_renderer && m_renderer->isRenderMathMLUnderOver();
125}
126
127bool AccessibilityMathMLElement::isMathSquareRoot() const
128{
129 return m_renderer && m_renderer->isRenderMathMLSquareRoot();
130}
131
132bool AccessibilityMathMLElement::isMathToken() const
133{
134 return m_renderer && m_renderer->isRenderMathMLToken();
135}
136
137bool AccessibilityMathMLElement::isMathRoot() const
138{
139 return m_renderer && m_renderer->isRenderMathMLRoot();
140}
141
142bool AccessibilityMathMLElement::isMathOperator() const
143{
144 return m_renderer && m_renderer->isRenderMathMLOperator();
145}
146
147bool AccessibilityMathMLElement::isAnonymousMathOperator() const
148{
149 return m_isAnonymousOperator;
150}
151
152bool AccessibilityMathMLElement::isMathFenceOperator() const
153{
154 if (!is<RenderMathMLOperator>(renderer()))
155 return false;
156
157 return downcast<RenderMathMLOperator>(*m_renderer).hasOperatorFlag(MathMLOperatorDictionary::Fence);
158}
159
160bool AccessibilityMathMLElement::isMathSeparatorOperator() const
161{
162 if (!is<RenderMathMLOperator>(renderer()))
163 return false;
164
165 return downcast<RenderMathMLOperator>(*m_renderer).hasOperatorFlag(MathMLOperatorDictionary::Separator);
166}
167
168bool AccessibilityMathMLElement::isMathText() const
169{
170 return node() && (node()->hasTagName(MathMLNames::mtextTag) || hasTagName(MathMLNames::msTag));
171}
172
173bool AccessibilityMathMLElement::isMathNumber() const
174{
175 return node() && node()->hasTagName(MathMLNames::mnTag);
176}
177
178bool AccessibilityMathMLElement::isMathIdentifier() const
179{
180 return node() && node()->hasTagName(MathMLNames::miTag);
181}
182
183bool AccessibilityMathMLElement::isMathMultiscript() const
184{
185 return node() && node()->hasTagName(MathMLNames::mmultiscriptsTag);
186}
187
188bool AccessibilityMathMLElement::isMathTable() const
189{
190 return node() && node()->hasTagName(MathMLNames::mtableTag);
191}
192
193bool AccessibilityMathMLElement::isMathTableRow() const
194{
195 return node() && (node()->hasTagName(MathMLNames::mtrTag) || hasTagName(MathMLNames::mlabeledtrTag));
196}
197
198bool AccessibilityMathMLElement::isMathTableCell() const
199{
200 return node() && node()->hasTagName(MathMLNames::mtdTag);
201}
202
203bool AccessibilityMathMLElement::isMathScriptObject(AccessibilityMathScriptObjectType type) const
204{
205 AccessibilityObject* parent = parentObjectUnignored();
206 if (!parent)
207 return false;
208
209 return type == AccessibilityMathScriptObjectType::Subscript ? this == parent->mathSubscriptObject() : this == parent->mathSuperscriptObject();
210}
211
212bool AccessibilityMathMLElement::isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType type) const
213{
214 AccessibilityObject* parent = parentObjectUnignored();
215 if (!parent || !parent->isMathMultiscript())
216 return false;
217
218 // The scripts in a MathML <mmultiscripts> element consist of one or more
219 // subscript, superscript pairs. In order to determine if this object is
220 // a scripted token, we need to examine each set of pairs to see if the
221 // this token is present and in the position corresponding with the type.
222
223 AccessibilityMathMultiscriptPairs pairs;
224 if (type == AccessibilityMathMultiscriptObjectType::PreSubscript || type == AccessibilityMathMultiscriptObjectType::PreSuperscript)
225 parent->mathPrescripts(pairs);
226 else
227 parent->mathPostscripts(pairs);
228
229 for (const auto& pair : pairs) {
230 if (this == pair.first)
231 return (type == AccessibilityMathMultiscriptObjectType::PreSubscript || type == AccessibilityMathMultiscriptObjectType::PostSubscript);
232 if (this == pair.second)
233 return (type == AccessibilityMathMultiscriptObjectType::PreSuperscript || type == AccessibilityMathMultiscriptObjectType::PostSuperscript);
234 }
235
236 return false;
237}
238
239AccessibilityObject* AccessibilityMathMLElement::mathRadicandObject()
240{
241 if (!isMathRoot())
242 return nullptr;
243
244 // For MathSquareRoot, we actually return the first child of the base.
245 // See also https://webkit.org/b/146452
246 const auto& children = this->children();
247 if (children.size() < 1)
248 return nullptr;
249
250 return children[0].get();
251}
252
253AccessibilityObject* AccessibilityMathMLElement::mathRootIndexObject()
254{
255 if (!isMathRoot() || isMathSquareRoot())
256 return nullptr;
257
258 const auto& children = this->children();
259 if (children.size() < 2)
260 return nullptr;
261
262 return children[1].get();
263}
264
265AccessibilityObject* AccessibilityMathMLElement::mathNumeratorObject()
266{
267 if (!isMathFraction())
268 return nullptr;
269
270 const auto& children = this->children();
271 if (children.size() != 2)
272 return nullptr;
273
274 return children[0].get();
275}
276
277AccessibilityObject* AccessibilityMathMLElement::mathDenominatorObject()
278{
279 if (!isMathFraction())
280 return nullptr;
281
282 const auto& children = this->children();
283 if (children.size() != 2)
284 return nullptr;
285
286 return children[1].get();
287}
288
289AccessibilityObject* AccessibilityMathMLElement::mathUnderObject()
290{
291 if (!isMathUnderOver() || !node())
292 return nullptr;
293
294 const auto& children = this->children();
295 if (children.size() < 2)
296 return nullptr;
297
298 if (node()->hasTagName(MathMLNames::munderTag) || node()->hasTagName(MathMLNames::munderoverTag))
299 return children[1].get();
300
301 return nullptr;
302}
303
304AccessibilityObject* AccessibilityMathMLElement::mathOverObject()
305{
306 if (!isMathUnderOver() || !node())
307 return nullptr;
308
309 const auto& children = this->children();
310 if (children.size() < 2)
311 return nullptr;
312
313 if (node()->hasTagName(MathMLNames::moverTag))
314 return children[1].get();
315 if (node()->hasTagName(MathMLNames::munderoverTag))
316 return children[2].get();
317
318 return nullptr;
319}
320
321AccessibilityObject* AccessibilityMathMLElement::mathBaseObject()
322{
323 if (!isMathSubscriptSuperscript() && !isMathUnderOver() && !isMathMultiscript())
324 return nullptr;
325
326 const auto& children = this->children();
327 // The base object in question is always the first child.
328 if (children.size() > 0)
329 return children[0].get();
330
331 return nullptr;
332}
333
334AccessibilityObject* AccessibilityMathMLElement::mathSubscriptObject()
335{
336 if (!isMathSubscriptSuperscript() || !node())
337 return nullptr;
338
339 const auto& children = this->children();
340 if (children.size() < 2)
341 return nullptr;
342
343 if (node()->hasTagName(MathMLNames::msubTag) || node()->hasTagName(MathMLNames::msubsupTag))
344 return children[1].get();
345
346 return nullptr;
347}
348
349AccessibilityObject* AccessibilityMathMLElement::mathSuperscriptObject()
350{
351 if (!isMathSubscriptSuperscript() || !node())
352 return nullptr;
353
354 const auto& children = this->children();
355 unsigned count = children.size();
356
357 if (count >= 2 && node()->hasTagName(MathMLNames::msupTag))
358 return children[1].get();
359
360 if (count >= 3 && node()->hasTagName(MathMLNames::msubsupTag))
361 return children[2].get();
362
363 return nullptr;
364}
365
366String AccessibilityMathMLElement::mathFencedOpenString() const
367{
368 if (!isMathFenced())
369 return String();
370
371 return getAttribute(MathMLNames::openAttr);
372}
373
374String AccessibilityMathMLElement::mathFencedCloseString() const
375{
376 if (!isMathFenced())
377 return String();
378
379 return getAttribute(MathMLNames::closeAttr);
380}
381
382void AccessibilityMathMLElement::mathPrescripts(AccessibilityMathMultiscriptPairs& prescripts)
383{
384 if (!isMathMultiscript() || !node())
385 return;
386
387 bool foundPrescript = false;
388 std::pair<AccessibilityObject*, AccessibilityObject*> prescriptPair;
389 for (Node* child = node()->firstChild(); child; child = child->nextSibling()) {
390 if (foundPrescript) {
391 AccessibilityObject* axChild = axObjectCache()->getOrCreate(child);
392 if (axChild && axChild->isMathElement()) {
393 if (!prescriptPair.first)
394 prescriptPair.first = axChild;
395 else {
396 prescriptPair.second = axChild;
397 prescripts.append(prescriptPair);
398 prescriptPair.first = nullptr;
399 prescriptPair.second = nullptr;
400 }
401 }
402 } else if (child->hasTagName(MathMLNames::mprescriptsTag))
403 foundPrescript = true;
404 }
405
406 // Handle the odd number of pre scripts case.
407 if (prescriptPair.first)
408 prescripts.append(prescriptPair);
409}
410
411void AccessibilityMathMLElement::mathPostscripts(AccessibilityMathMultiscriptPairs& postscripts)
412{
413 if (!isMathMultiscript() || !node())
414 return;
415
416 // In Multiscripts, the post-script elements start after the first element (which is the base)
417 // and continue until a <mprescripts> tag is found
418 std::pair<AccessibilityObject*, AccessibilityObject*> postscriptPair;
419 bool foundBaseElement = false;
420 for (Node* child = node()->firstChild(); child; child = child->nextSibling()) {
421 if (child->hasTagName(MathMLNames::mprescriptsTag))
422 break;
423
424 AccessibilityObject* axChild = axObjectCache()->getOrCreate(child);
425 if (axChild && axChild->isMathElement()) {
426 if (!foundBaseElement)
427 foundBaseElement = true;
428 else if (!postscriptPair.first)
429 postscriptPair.first = axChild;
430 else {
431 postscriptPair.second = axChild;
432 postscripts.append(postscriptPair);
433 postscriptPair.first = nullptr;
434 postscriptPair.second = nullptr;
435 }
436 }
437 }
438
439 // Handle the odd number of post scripts case.
440 if (postscriptPair.first)
441 postscripts.append(postscriptPair);
442}
443
444int AccessibilityMathMLElement::mathLineThickness() const
445{
446 if (!is<RenderMathMLFraction>(renderer()))
447 return -1;
448
449 return downcast<RenderMathMLFraction>(*m_renderer).relativeLineThickness();
450}
451
452} // namespace WebCore
453
454#endif // ENABLE(MATHML)
455