| /* |
| * 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" |
| #include "NodeInlines.h" |
| #include "RenderStyleInlines.h" |
| |
| namespace WebCore { |
| |
| AccessibilityMathMLElement::AccessibilityMathMLElement(AXID axID, RenderObject& renderer, AXObjectCache& cache, bool isAnonymousOperator) |
| : AccessibilityRenderObject(axID, renderer, cache) |
| , m_isAnonymousOperator(isAnonymousOperator) |
| { |
| } |
| |
| AccessibilityMathMLElement::~AccessibilityMathMLElement() = default; |
| |
| Ref<AccessibilityMathMLElement> AccessibilityMathMLElement::create(AXID axID, RenderObject& renderer, AXObjectCache& cache, bool isAnonymousOperator) |
| { |
| return adoptRef(*new AccessibilityMathMLElement(axID, renderer, cache, isAnonymousOperator)); |
| } |
| |
| AccessibilityRole AccessibilityMathMLElement::determineAccessibilityRole() |
| { |
| if (!m_renderer) |
| return AccessibilityRole::Unknown; |
| |
| if ((m_ariaRole = determineAriaRoleAttribute()) != AccessibilityRole::Unknown) |
| return m_ariaRole; |
| |
| if (WebCore::elementName(m_renderer->protectedNode().get()) == ElementName::MathML_math) |
| 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; |
| } |
| |
| void AccessibilityMathMLElement::addChildren() |
| { |
| if (!hasElementName(ElementName::MathML_mfenced)) { |
| AccessibilityRenderObject::addChildren(); |
| return; |
| } |
| |
| // mfenced elements generate lots of anonymous renderers due to their `open`, `close`, and `separators` attributes. |
| // Because of this, default to walking the render tree when adding their children (unlike most other object types for |
| // which we walk the DOM). This may cause unexpected behavior for `display:contents` descendants of mfenced elements. |
| // However, this element is very deprecated, and even the most simple usages of it do not render consistently across |
| // browsers, so it's already unlikely to be used by web developers, even more so with `display:contents` mixed in. |
| m_childrenInitialized = true; |
| for (Ref object : AXChildIterator(*this)) |
| addChild(WTFMove(object)); |
| |
| m_subtreeDirty = false; |
| |
| #ifndef NDEBUG |
| verifyChildrenIndexInParent(); |
| #endif |
| } |
| |
| String AccessibilityMathMLElement::textUnderElement(TextUnderElementMode mode) const |
| { |
| if (m_isAnonymousOperator && !mode.isHidden()) { |
| char16_t operatorChar = downcast<RenderMathMLOperator>(*m_renderer).textContent(); |
| return operatorChar ? String(span(operatorChar)) : 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() && !isMathUnderOver() && !isMathMultiscript() && !isMathFraction(); |
| } |
| |
| 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::isMathFenceOperator() const |
| { |
| auto* mathMLOperator = dynamicDowncast<RenderMathMLOperator>(renderer()); |
| return mathMLOperator && mathMLOperator->hasOperatorFlag(MathMLOperatorDictionary::Fence); |
| } |
| |
| bool AccessibilityMathMLElement::isMathSeparatorOperator() const |
| { |
| auto* mathMLOperator = dynamicDowncast<RenderMathMLOperator>(renderer()); |
| return mathMLOperator && mathMLOperator->hasOperatorFlag(MathMLOperatorDictionary::Separator); |
| } |
| |
| bool AccessibilityMathMLElement::isMathText() const |
| { |
| auto elementName = this->elementName(); |
| return elementName == ElementName::MathML_mtext || elementName == ElementName::MathML_ms; |
| } |
| |
| bool AccessibilityMathMLElement::isMathNumber() const |
| { |
| return elementName() == ElementName::MathML_mn; |
| } |
| |
| bool AccessibilityMathMLElement::isMathIdentifier() const |
| { |
| return elementName() == ElementName::MathML_mi; |
| } |
| |
| bool AccessibilityMathMLElement::isMathMultiscript() const |
| { |
| return elementName() == ElementName::MathML_mmultiscripts; |
| } |
| |
| bool AccessibilityMathMLElement::isMathTable() const |
| { |
| return elementName() == ElementName::MathML_mtable; |
| } |
| |
| bool AccessibilityMathMLElement::isMathTableRow() const |
| { |
| auto elementName = this->elementName(); |
| return elementName == ElementName::MathML_mtr || elementName == ElementName::MathML_mlabeledtr; |
| } |
| |
| bool AccessibilityMathMLElement::isMathTableCell() const |
| { |
| return elementName() == ElementName::MathML_mtd; |
| } |
| |
| bool AccessibilityMathMLElement::isMathScriptObject(AccessibilityMathScriptObjectType type) const |
| { |
| RefPtr parent = parentObjectUnignored(); |
| if (!parent) |
| return false; |
| |
| return type == AccessibilityMathScriptObjectType::Subscript ? this == parent->mathSubscriptObject() : this == parent->mathSuperscriptObject(); |
| } |
| |
| bool AccessibilityMathMLElement::isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType type) const |
| { |
| RefPtr 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; |
| } |
| |
| std::optional<AXCoreObject::AccessibilityChildrenVector> AccessibilityMathMLElement::mathRadicand() |
| { |
| if (!isMathRoot()) |
| return std::nullopt; |
| |
| const auto& children = this->unignoredChildren(); |
| if (!children.size()) |
| return std::nullopt; |
| |
| if (isMathSquareRoot()) |
| return children; |
| return { { children[0] } }; |
| } |
| |
| AXCoreObject* AccessibilityMathMLElement::mathRootIndexObject() |
| { |
| if (!isMathRoot() || isMathSquareRoot()) |
| return nullptr; |
| |
| const auto& children = this->unignoredChildren(); |
| if (children.size() < 2) |
| return nullptr; |
| |
| return children[1].ptr(); |
| } |
| |
| AXCoreObject* AccessibilityMathMLElement::mathNumeratorObject() |
| { |
| if (!isMathFraction()) |
| return nullptr; |
| |
| const auto& children = this->unignoredChildren(); |
| if (children.size() != 2) |
| return nullptr; |
| |
| return children[0].ptr(); |
| } |
| |
| AXCoreObject* AccessibilityMathMLElement::mathDenominatorObject() |
| { |
| if (!isMathFraction()) |
| return nullptr; |
| |
| const auto& children = this->unignoredChildren(); |
| if (children.size() != 2) |
| return nullptr; |
| |
| return children[1].ptr(); |
| } |
| |
| AXCoreObject* AccessibilityMathMLElement::mathUnderObject() |
| { |
| if (!isMathUnderOver() || !element()) |
| return nullptr; |
| |
| const auto& children = this->unignoredChildren(); |
| if (children.size() < 2) |
| return nullptr; |
| |
| auto elementName = this->elementName(); |
| if (elementName == ElementName::MathML_munder || elementName == ElementName::MathML_munderover) |
| return children[1].ptr(); |
| |
| return nullptr; |
| } |
| |
| AXCoreObject* AccessibilityMathMLElement::mathOverObject() |
| { |
| if (!isMathUnderOver() || !node()) |
| return nullptr; |
| |
| const auto& children = unignoredChildren(); |
| auto elementName = this->elementName(); |
| if (children.size() >= 2 && elementName == ElementName::MathML_mover) |
| return children[1].ptr(); |
| |
| if (children.size() >= 3 && elementName == ElementName::MathML_munderover) |
| return children[2].ptr(); |
| |
| return nullptr; |
| } |
| |
| AXCoreObject* AccessibilityMathMLElement::mathBaseObject() |
| { |
| if (!isMathSubscriptSuperscript() && !isMathUnderOver() && !isMathMultiscript()) |
| return nullptr; |
| |
| const auto& children = unignoredChildren(); |
| // The base object in question is always the first child. |
| if (children.size() > 0) |
| return children[0].ptr(); |
| |
| return nullptr; |
| } |
| |
| AXCoreObject* AccessibilityMathMLElement::mathSubscriptObject() |
| { |
| if (!isMathSubscriptSuperscript() || !node()) |
| return nullptr; |
| |
| const auto& children = unignoredChildren(); |
| if (children.size() < 2) |
| return nullptr; |
| |
| auto elementName = this->elementName(); |
| if (elementName == ElementName::MathML_msub || elementName == ElementName::MathML_msubsup) |
| return children[1].ptr(); |
| |
| return nullptr; |
| } |
| |
| AXCoreObject* AccessibilityMathMLElement::mathSuperscriptObject() |
| { |
| if (!isMathSubscriptSuperscript() || !node()) |
| return nullptr; |
| |
| const auto& children = unignoredChildren(); |
| unsigned count = children.size(); |
| |
| auto elementName = this->elementName(); |
| if (count >= 2 && elementName == ElementName::MathML_msup) |
| return children[1].ptr(); |
| |
| if (count >= 3 && elementName == ElementName::MathML_msubsup) |
| return children[2].ptr(); |
| |
| 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 (RefPtr child = node()->firstChild(); child; child = child->nextSibling()) { |
| if (foundPrescript) { |
| RefPtr axChild = axObjectCache()->getOrCreate(*child); |
| if (axChild && axChild->isMathElement()) { |
| if (!prescriptPair.first) |
| prescriptPair.first = axChild.get(); |
| else { |
| prescriptPair.second = axChild.get(); |
| prescripts.append(prescriptPair); |
| prescriptPair.first = nullptr; |
| prescriptPair.second = nullptr; |
| } |
| } |
| } else if (WebCore::elementName(*child) == ElementName::MathML_mprescripts) |
| 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 (RefPtr child = node()->firstChild(); child; child = child->nextSibling()) { |
| if (WebCore::elementName(*child) == ElementName::MathML_mprescripts) |
| break; |
| |
| RefPtr axChild = axObjectCache()->getOrCreate(*child); |
| if (axChild && axChild->isMathElement()) { |
| if (!foundBaseElement) |
| foundBaseElement = true; |
| else if (!postscriptPair.first) |
| postscriptPair.first = axChild.get(); |
| else { |
| postscriptPair.second = axChild.get(); |
| 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 |
| { |
| auto* fraction = dynamicDowncast<RenderMathMLFraction>(renderer()); |
| if (!fraction) |
| return -1; |
| |
| return fraction->relativeLineThickness(); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(MATHML) |