blob: aaeafe084c8e7dbbc9e6d60f56a6337b52d1ddab [file] [log] [blame]
/*
* Copyright (C) 2016 Igalia, S.L.
* 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 THE COPYRIGHT
* OWNER 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.
*/
#include "config.h"
#if ENABLE(MATHML)
#include "AccessibilityMathMLElement.h"
#include "AXObjectCache.h"
#include "MathMLNames.h"
namespace WebCore {
AccessibilityMathMLElement::AccessibilityMathMLElement(RenderObject* renderer, bool isAnonymousOperator)
: AccessibilityRenderObject(renderer)
, m_isAnonymousOperator(isAnonymousOperator)
{
}
AccessibilityMathMLElement::~AccessibilityMathMLElement() = default;
Ref<AccessibilityMathMLElement> AccessibilityMathMLElement::create(RenderObject* renderer, bool isAnonymousOperator)
{
return adoptRef(*new AccessibilityMathMLElement(renderer, isAnonymousOperator));
}
AccessibilityRole AccessibilityMathMLElement::determineAccessibilityRole()
{
if (!m_renderer)
return AccessibilityRole::Unknown;
if ((m_ariaRole = determineAriaRoleAttribute()) != AccessibilityRole::Unknown)
return m_ariaRole;
Node* node = m_renderer->node();
if (node && node->hasTagName(MathMLNames::mathTag))
return AccessibilityRole::DocumentMath;
// It's not clear which role a platform should choose for a math element.
// Declaring a math element role should give flexibility to platforms to choose.
return AccessibilityRole::MathElement;
}
String AccessibilityMathMLElement::textUnderElement(AccessibilityTextUnderElementMode mode) const
{
if (m_isAnonymousOperator) {
UChar operatorChar = downcast<RenderMathMLOperator>(*m_renderer).textContent();
return operatorChar ? String(&operatorChar, 1) : String();
}
return AccessibilityRenderObject::textUnderElement(mode);
}
String AccessibilityMathMLElement::stringValue() const
{
if (m_isAnonymousOperator)
return textUnderElement();
return AccessibilityRenderObject::stringValue();
}
bool AccessibilityMathMLElement::isIgnoredElementWithinMathTree() const
{
if (m_isAnonymousOperator)
return false;
// Only math elements that we explicitly recognize should be included
// We don't want things like <mstyle> to appear in the tree.
if (isMathFraction() || isMathFenced() || isMathSubscriptSuperscript() || isMathRow()
|| isMathUnderOver() || isMathRoot() || isMathText() || isMathNumber()
|| isMathOperator() || isMathFenceOperator() || isMathSeparatorOperator()
|| isMathIdentifier() || isMathTable() || isMathTableRow() || isMathTableCell() || isMathMultiscript())
return false;
return true;
}
bool AccessibilityMathMLElement::isMathFraction() const
{
return m_renderer && m_renderer->isRenderMathMLFraction();
}
bool AccessibilityMathMLElement::isMathFenced() const
{
return m_renderer && m_renderer->isRenderMathMLFenced();
}
bool AccessibilityMathMLElement::isMathSubscriptSuperscript() const
{
return m_renderer && m_renderer->isRenderMathMLScripts() && !isMathMultiscript();
}
bool AccessibilityMathMLElement::isMathRow() const
{
return m_renderer && m_renderer->isRenderMathMLRow() && !isMathRoot();
}
bool AccessibilityMathMLElement::isMathUnderOver() const
{
return m_renderer && m_renderer->isRenderMathMLUnderOver();
}
bool AccessibilityMathMLElement::isMathSquareRoot() const
{
return m_renderer && m_renderer->isRenderMathMLSquareRoot();
}
bool AccessibilityMathMLElement::isMathToken() const
{
return m_renderer && m_renderer->isRenderMathMLToken();
}
bool AccessibilityMathMLElement::isMathRoot() const
{
return m_renderer && m_renderer->isRenderMathMLRoot();
}
bool AccessibilityMathMLElement::isMathOperator() const
{
return m_renderer && m_renderer->isRenderMathMLOperator();
}
bool AccessibilityMathMLElement::isAnonymousMathOperator() const
{
return m_isAnonymousOperator;
}
bool AccessibilityMathMLElement::isMathFenceOperator() const
{
if (!is<RenderMathMLOperator>(renderer()))
return false;
return downcast<RenderMathMLOperator>(*m_renderer).hasOperatorFlag(MathMLOperatorDictionary::Fence);
}
bool AccessibilityMathMLElement::isMathSeparatorOperator() const
{
if (!is<RenderMathMLOperator>(renderer()))
return false;
return downcast<RenderMathMLOperator>(*m_renderer).hasOperatorFlag(MathMLOperatorDictionary::Separator);
}
bool AccessibilityMathMLElement::isMathText() const
{
return node() && (node()->hasTagName(MathMLNames::mtextTag) || hasTagName(MathMLNames::msTag));
}
bool AccessibilityMathMLElement::isMathNumber() const
{
return node() && node()->hasTagName(MathMLNames::mnTag);
}
bool AccessibilityMathMLElement::isMathIdentifier() const
{
return node() && node()->hasTagName(MathMLNames::miTag);
}
bool AccessibilityMathMLElement::isMathMultiscript() const
{
return node() && node()->hasTagName(MathMLNames::mmultiscriptsTag);
}
bool AccessibilityMathMLElement::isMathTable() const
{
return node() && node()->hasTagName(MathMLNames::mtableTag);
}
bool AccessibilityMathMLElement::isMathTableRow() const
{
return node() && (node()->hasTagName(MathMLNames::mtrTag) || hasTagName(MathMLNames::mlabeledtrTag));
}
bool AccessibilityMathMLElement::isMathTableCell() const
{
return node() && node()->hasTagName(MathMLNames::mtdTag);
}
bool AccessibilityMathMLElement::isMathScriptObject(AccessibilityMathScriptObjectType type) const
{
AXCoreObject* parent = parentObjectUnignored();
if (!parent)
return false;
return type == AccessibilityMathScriptObjectType::Subscript ? this == parent->mathSubscriptObject() : this == parent->mathSuperscriptObject();
}
bool AccessibilityMathMLElement::isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType type) const
{
AXCoreObject* parent = parentObjectUnignored();
if (!parent || !parent->isMathMultiscript())
return false;
// The scripts in a MathML <mmultiscripts> element consist of one or more
// subscript, superscript pairs. In order to determine if this object is
// a scripted token, we need to examine each set of pairs to see if the
// this token is present and in the position corresponding with the type.
AccessibilityMathMultiscriptPairs pairs;
if (type == AccessibilityMathMultiscriptObjectType::PreSubscript || type == AccessibilityMathMultiscriptObjectType::PreSuperscript)
parent->mathPrescripts(pairs);
else
parent->mathPostscripts(pairs);
for (const auto& pair : pairs) {
if (this == pair.first)
return (type == AccessibilityMathMultiscriptObjectType::PreSubscript || type == AccessibilityMathMultiscriptObjectType::PostSubscript);
if (this == pair.second)
return (type == AccessibilityMathMultiscriptObjectType::PreSuperscript || type == AccessibilityMathMultiscriptObjectType::PostSuperscript);
}
return false;
}
AXCoreObject* AccessibilityMathMLElement::mathRadicandObject()
{
if (!isMathRoot())
return nullptr;
// For MathSquareRoot, we actually return the first child of the base.
// See also https://webkit.org/b/146452
const auto& children = this->children();
if (children.size() < 1)
return nullptr;
return children[0].get();
}
AXCoreObject* AccessibilityMathMLElement::mathRootIndexObject()
{
if (!isMathRoot() || isMathSquareRoot())
return nullptr;
const auto& children = this->children();
if (children.size() < 2)
return nullptr;
return children[1].get();
}
AXCoreObject* AccessibilityMathMLElement::mathNumeratorObject()
{
if (!isMathFraction())
return nullptr;
const auto& children = this->children();
if (children.size() != 2)
return nullptr;
return children[0].get();
}
AXCoreObject* AccessibilityMathMLElement::mathDenominatorObject()
{
if (!isMathFraction())
return nullptr;
const auto& children = this->children();
if (children.size() != 2)
return nullptr;
return children[1].get();
}
AXCoreObject* AccessibilityMathMLElement::mathUnderObject()
{
if (!isMathUnderOver() || !node())
return nullptr;
const auto& children = this->children();
if (children.size() < 2)
return nullptr;
if (node()->hasTagName(MathMLNames::munderTag) || node()->hasTagName(MathMLNames::munderoverTag))
return children[1].get();
return nullptr;
}
AXCoreObject* AccessibilityMathMLElement::mathOverObject()
{
if (!isMathUnderOver() || !node())
return nullptr;
const auto& children = this->children();
if (children.size() < 2)
return nullptr;
if (node()->hasTagName(MathMLNames::moverTag))
return children[1].get();
if (node()->hasTagName(MathMLNames::munderoverTag))
return children[2].get();
return nullptr;
}
AXCoreObject* AccessibilityMathMLElement::mathBaseObject()
{
if (!isMathSubscriptSuperscript() && !isMathUnderOver() && !isMathMultiscript())
return nullptr;
const auto& children = this->children();
// The base object in question is always the first child.
if (children.size() > 0)
return children[0].get();
return nullptr;
}
AXCoreObject* AccessibilityMathMLElement::mathSubscriptObject()
{
if (!isMathSubscriptSuperscript() || !node())
return nullptr;
const auto& children = this->children();
if (children.size() < 2)
return nullptr;
if (node()->hasTagName(MathMLNames::msubTag) || node()->hasTagName(MathMLNames::msubsupTag))
return children[1].get();
return nullptr;
}
AXCoreObject* AccessibilityMathMLElement::mathSuperscriptObject()
{
if (!isMathSubscriptSuperscript() || !node())
return nullptr;
const auto& children = this->children();
unsigned count = children.size();
if (count >= 2 && node()->hasTagName(MathMLNames::msupTag))
return children[1].get();
if (count >= 3 && node()->hasTagName(MathMLNames::msubsupTag))
return children[2].get();
return nullptr;
}
String AccessibilityMathMLElement::mathFencedOpenString() const
{
if (!isMathFenced())
return String();
return getAttribute(MathMLNames::openAttr);
}
String AccessibilityMathMLElement::mathFencedCloseString() const
{
if (!isMathFenced())
return String();
return getAttribute(MathMLNames::closeAttr);
}
void AccessibilityMathMLElement::mathPrescripts(AccessibilityMathMultiscriptPairs& prescripts)
{
if (!isMathMultiscript() || !node())
return;
bool foundPrescript = false;
std::pair<AccessibilityObject*, AccessibilityObject*> prescriptPair;
for (Node* child = node()->firstChild(); child; child = child->nextSibling()) {
if (foundPrescript) {
AccessibilityObject* axChild = axObjectCache()->getOrCreate(child);
if (axChild && axChild->isMathElement()) {
if (!prescriptPair.first)
prescriptPair.first = axChild;
else {
prescriptPair.second = axChild;
prescripts.append(prescriptPair);
prescriptPair.first = nullptr;
prescriptPair.second = nullptr;
}
}
} else if (child->hasTagName(MathMLNames::mprescriptsTag))
foundPrescript = true;
}
// Handle the odd number of pre scripts case.
if (prescriptPair.first)
prescripts.append(prescriptPair);
}
void AccessibilityMathMLElement::mathPostscripts(AccessibilityMathMultiscriptPairs& postscripts)
{
if (!isMathMultiscript() || !node())
return;
// In Multiscripts, the post-script elements start after the first element (which is the base)
// and continue until a <mprescripts> tag is found
std::pair<AccessibilityObject*, AccessibilityObject*> postscriptPair;
bool foundBaseElement = false;
for (Node* child = node()->firstChild(); child; child = child->nextSibling()) {
if (child->hasTagName(MathMLNames::mprescriptsTag))
break;
AccessibilityObject* axChild = axObjectCache()->getOrCreate(child);
if (axChild && axChild->isMathElement()) {
if (!foundBaseElement)
foundBaseElement = true;
else if (!postscriptPair.first)
postscriptPair.first = axChild;
else {
postscriptPair.second = axChild;
postscripts.append(postscriptPair);
postscriptPair.first = nullptr;
postscriptPair.second = nullptr;
}
}
}
// Handle the odd number of post scripts case.
if (postscriptPair.first)
postscripts.append(postscriptPair);
}
int AccessibilityMathMLElement::mathLineThickness() const
{
if (!is<RenderMathMLFraction>(renderer()))
return -1;
return downcast<RenderMathMLFraction>(*m_renderer).relativeLineThickness();
}
} // namespace WebCore
#endif // ENABLE(MATHML)