1/* 2 * Copyright (C) 2013 The MathJax Consortium. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 15 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 16 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 17 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "MathMLSelectElement.h" 28 29#if ENABLE(MATHML) 30 31#include "Event.h" 32#include "HTMLElement.h" 33#include "HTMLNames.h" 34#include "MathMLNames.h" 35#include "RenderMathMLRow.h" 36#include "SVGElement.h" 37#include "SVGNames.h" 38 39namespace WebCore { 40 41using namespace MathMLNames; 42 43MathMLSelectElement::MathMLSelectElement(const QualifiedName& tagName, Document& document) 44 : MathMLInlineContainerElement(tagName, document) 45 , m_selectedChild(nullptr) 46{ 47} 48 49PassRefPtr<MathMLSelectElement> MathMLSelectElement::create(const QualifiedName& tagName, Document& document) 50{ 51 return adoptRef(new MathMLSelectElement(tagName, document)); 52} 53 54RenderPtr<RenderElement> MathMLSelectElement::createElementRenderer(PassRef<RenderStyle> style) 55{ 56 return createRenderer<RenderMathMLRow>(*this, WTF::move(style)); 57} 58 59// We recognize the following values for the encoding attribute of the <semantics> element: 60// 61// - "MathML-Presentation", which is mentioned in the MathML 3 recommendation. 62// - "SVG1.1" which is mentioned in the W3C note. 63// http://www.w3.org/Math/Documents/Notes/graphics.xml 64// - Other MIME Content-Types for MathML, SVG and HTML. 65// 66// We exclude "application/mathml+xml" which is ambiguous about whether it is Presentation or Content MathML. Authors must use a more explicit encoding value. 67bool MathMLSelectElement::isMathMLEncoding(const AtomicString& value) 68{ 69 return value == "application/mathml-presentation+xml" || value == "MathML-Presentation"; 70} 71 72bool MathMLSelectElement::isSVGEncoding(const AtomicString& value) 73{ 74 return value == "image/svg+xml" || value == "SVG1.1"; 75} 76 77bool MathMLSelectElement::isHTMLEncoding(const AtomicString& value) 78{ 79 return value == "application/xhtml+xml" || value == "text/html"; 80} 81 82bool MathMLSelectElement::childShouldCreateRenderer(const Node& child) const 83{ 84 return MathMLElement::childShouldCreateRenderer(child) && m_selectedChild == &child; 85} 86 87void MathMLSelectElement::finishParsingChildren() 88{ 89 updateSelectedChild(); 90 MathMLInlineContainerElement::finishParsingChildren(); 91} 92 93void MathMLSelectElement::childrenChanged(const ChildChange& change) 94{ 95 updateSelectedChild(); 96 MathMLInlineContainerElement::childrenChanged(change); 97} 98 99void MathMLSelectElement::attributeChanged(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue, AttributeModificationReason reason) 100{ 101 if (hasTagName(mactionTag) && (name == MathMLNames::actiontypeAttr || name == MathMLNames::selectionAttr)) 102 updateSelectedChild(); 103 104 MathMLInlineContainerElement::attributeChanged(name, oldValue, newValue, reason); 105} 106 107int MathMLSelectElement::getSelectedActionChildAndIndex(Element*& selectedChild) 108{ 109 ASSERT(hasTagName(mactionTag)); 110 111 // We "round up or down to the closest allowable value" of the selection attribute, as suggested by the MathML specification. 112 selectedChild = firstElementChild(); 113 if (!selectedChild) 114 return 1; 115 116 int selection = fastGetAttribute(MathMLNames::selectionAttr).toInt(); 117 int i; 118 for (i = 1; i < selection; i++) { 119 Element* nextChild = selectedChild->nextElementSibling(); 120 if (!nextChild) 121 break; 122 selectedChild = nextChild; 123 } 124 125 return i; 126} 127 128Element* MathMLSelectElement::getSelectedActionChild() 129{ 130 ASSERT(hasTagName(mactionTag)); 131 132 Element* child = firstElementChild(); 133 if (!child) 134 return child; 135 136 // The value of the actiontype attribute is case-sensitive. 137 const AtomicString& actiontype = fastGetAttribute(MathMLNames::actiontypeAttr); 138 if (actiontype == "statusline") 139 // FIXME: implement user interaction for the "statusline" action type (http://wkbug/124922). 140 { } 141 else if (actiontype == "tooltip") 142 // FIXME: implement user interaction for the "tooltip" action type (http://wkbug/124921). 143 { } 144 else { 145 // For the "toggle" action type or any unknown action type, we rely on the value of the selection attribute to determine the visible child. 146 getSelectedActionChildAndIndex(child); 147 } 148 149 return child; 150} 151 152Element* MathMLSelectElement::getSelectedSemanticsChild() 153{ 154 ASSERT(hasTagName(semanticsTag)); 155 156 Element* child = firstElementChild(); 157 if (!child) 158 return child; 159 160 if (!child->isMathMLElement() || !toMathMLElement(child)->isPresentationMathML()) { 161 // The first child is not a presentation MathML element. Hence we move to the second child and start searching an annotation child that could be displayed. 162 child = child->nextElementSibling(); 163 } else if (!toMathMLElement(child)->isSemanticAnnotation()) { 164 // The first child is a presentation MathML but not an annotation, so we can just display it. 165 return child; 166 } 167 // Otherwise, the first child is an <annotation> or <annotation-xml> element. This is invalid, but some people use this syntax so we take care of this case too and start the search from this first child. 168 169 for ( ; child; child = child->nextElementSibling()) { 170 if (!child->isMathMLElement()) 171 continue; 172 173 if (child->hasTagName(MathMLNames::annotationTag)) { 174 // If the <annotation> element has an src attribute then it is a reference to arbitrary binary data and it is not clear whether we can display it. Hence we just ignore the annotation. 175 if (child->hasAttribute(MathMLNames::srcAttr)) 176 continue; 177 // Otherwise, we assume it is a text annotation that can always be displayed and we stop here. 178 return child; 179 } 180 181 if (child->hasTagName(MathMLNames::annotation_xmlTag)) { 182 // If the <annotation-xml> element has an src attribute then it is a reference to arbitrary binary data and it is not clear whether we can display it. Hence we just ignore the annotation. 183 if (child->hasAttribute(MathMLNames::srcAttr)) 184 continue; 185 // If the <annotation-xml> element has an encoding attribute describing presentation MathML, SVG or HTML we assume the content can be displayed and we stop here. 186 const AtomicString& value = child->fastGetAttribute(MathMLNames::encodingAttr); 187 if (isMathMLEncoding(value) || isSVGEncoding(value) || isHTMLEncoding(value)) 188 return child; 189 } 190 } 191 192 // We fallback to the first child. 193 return firstElementChild(); 194} 195 196void MathMLSelectElement::updateSelectedChild() 197{ 198 Element* newSelectedChild = hasTagName(mactionTag) ? getSelectedActionChild() : getSelectedSemanticsChild(); 199 200 if (m_selectedChild == newSelectedChild) 201 return; 202 203 if (m_selectedChild && m_selectedChild->renderer()) 204 Style::detachRenderTree(*m_selectedChild); 205 206 m_selectedChild = newSelectedChild; 207 setNeedsStyleRecalc(); 208} 209 210void MathMLSelectElement::defaultEventHandler(Event* event) 211{ 212 if (event->type() == eventNames().clickEvent) { 213 if (fastGetAttribute(MathMLNames::actiontypeAttr) == "toggle") { 214 toggle(); 215 event->setDefaultHandled(); 216 return; 217 } 218 } 219 220 MathMLInlineContainerElement::defaultEventHandler(event); 221} 222 223bool MathMLSelectElement::willRespondToMouseClickEvents() 224{ 225 return fastGetAttribute(MathMLNames::actiontypeAttr) == "toggle"; 226} 227 228void MathMLSelectElement::toggle() 229{ 230 // Select the successor of the currently selected child 231 // or the first child if the currently selected child is the last. 232 Element* selectedChild; 233 int newSelectedChildIndex = getSelectedActionChildAndIndex(selectedChild) + 1; 234 if (!selectedChild || !selectedChild->nextElementSibling()) 235 newSelectedChildIndex = 1; 236 237 // We update the attribute value of the selection attribute. 238 // This will also call MathMLSelectElement::attributeChanged to update the selected child. 239 setAttribute(MathMLNames::selectionAttr, AtomicString::number(newSelectedChildIndex)); 240} 241 242} 243 244#endif // ENABLE(MATHML) 245