a127bfd854e995dad96b5ed79041731a36900603
[WebKit-https.git] / Source / WebCore / mathml / MathMLSelectElement.cpp
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 "MathMLNames.h"
33 #include "RenderMathMLRow.h"
34
35 namespace WebCore {
36
37 using namespace MathMLNames;
38
39 MathMLSelectElement::MathMLSelectElement(const QualifiedName& tagName, Document& document)
40     : MathMLInlineContainerElement(tagName, document)
41     , m_selectedChild(nullptr)
42 {
43 }
44
45 PassRefPtr<MathMLSelectElement> MathMLSelectElement::create(const QualifiedName& tagName, Document& document)
46 {
47     return adoptRef(new MathMLSelectElement(tagName, document));
48 }
49
50 RenderPtr<RenderElement> MathMLSelectElement::createElementRenderer(PassRef<RenderStyle> style)
51 {
52     return createRenderer<RenderMathMLRow>(*this, std::move(style));
53 }
54
55 bool MathMLSelectElement::childShouldCreateRenderer(const Node& child) const
56 {
57     return MathMLElement::childShouldCreateRenderer(child) && m_selectedChild == &child;
58 }
59
60 void MathMLSelectElement::finishParsingChildren()
61 {
62     updateSelectedChild();
63     MathMLInlineContainerElement::finishParsingChildren();
64 }
65
66 void MathMLSelectElement::childrenChanged(const ChildChange& change)
67 {
68     updateSelectedChild();
69     MathMLInlineContainerElement::childrenChanged(change);
70 }
71
72 void MathMLSelectElement::attributeChanged(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue, AttributeModificationReason reason)
73 {
74     if (hasLocalName(mactionTag) && (name == MathMLNames::actiontypeAttr || name == MathMLNames::selectionAttr))
75         updateSelectedChild();
76
77     MathMLInlineContainerElement::attributeChanged(name, oldValue, newValue, reason);
78 }
79
80 int MathMLSelectElement::getSelectedActionChildAndIndex(Element*& selectedChild)
81 {
82     ASSERT(hasLocalName(mactionTag));
83
84     // We "round up or down to the closest allowable value" of the selection attribute, as suggested by the MathML specification.
85     selectedChild = firstElementChild();
86     if (!selectedChild)
87         return 1;
88
89     int selection = fastGetAttribute(MathMLNames::selectionAttr).toInt();
90     int i;
91     for (i = 1; i < selection; i++) {
92         Element* nextChild = selectedChild->nextElementSibling();
93         if (!nextChild)
94             break;
95         selectedChild = nextChild;
96     }
97
98     return i;
99 }
100
101 Element* MathMLSelectElement::getSelectedActionChild()
102 {
103     ASSERT(hasLocalName(mactionTag));
104
105     Element* child = firstElementChild();
106     if (!child)
107         return child;
108
109     // The value of the actiontype attribute is case-sensitive.
110     const AtomicString& actiontype = fastGetAttribute(MathMLNames::actiontypeAttr);
111     if (actiontype == "statusline")
112         // FIXME: implement user interaction for the "statusline" action type (http://wkbug/124922).
113         { }
114     else if (actiontype == "tooltip")
115         // FIXME: implement user interaction for the "tooltip" action type (http://wkbug/124921).
116         { }
117     else {
118         // For the "toggle" action type or any unknown action type, we rely on the value of the selection attribute to determine the visible child.
119         getSelectedActionChildAndIndex(child);
120     }
121
122     return child;
123 }
124
125 Element* MathMLSelectElement::getSelectedSemanticsChild()
126 {
127     ASSERT(hasLocalName(semanticsTag));
128
129     Element* child = firstElementChild();
130     if (!child)
131         return child;
132
133     if (!child->isMathMLElement() || !toMathMLElement(child)->isPresentationMathML()) { 
134         // 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.
135         child = child->nextElementSibling();
136     } else if (!toMathMLElement(child)->isSemanticAnnotation()) {
137         // The first child is a presentation MathML but not an annotation, so we can just display it.
138         return child;
139     }
140     // 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.
141
142     for ( ; child; child = child->nextElementSibling()) {
143         if (!child->isMathMLElement())
144             continue;
145
146         if (child->hasLocalName(MathMLNames::annotationTag)) {
147             // 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.
148             if (child->hasAttribute(MathMLNames::srcAttr))
149                 continue;
150             // Otherwise, we assume it is a text annotation that can always be displayed and we stop here.
151             return child;
152         }
153
154         if (child->hasLocalName(MathMLNames::annotation_xmlTag)) {
155             // 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.
156             if (child->hasAttribute(MathMLNames::srcAttr))
157                 continue;
158             // 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. We recognize the following encoding values:
159             //
160             // - "MathML-Presentation", which is mentioned in the MathML 3 recommendation.
161             // - "SVG1.1" which is mentioned in the W3C note.
162             //   http://www.w3.org/Math/Documents/Notes/graphics.xml
163             // - Other MIME Content-Types for SVG and HTML.
164             //
165             // We exclude "application/mathml+xml" which is ambiguous about whether it is Presentation or Content MathML. Authors must use a more explicit encoding value.
166             const AtomicString& value = child->fastGetAttribute(MathMLNames::encodingAttr);
167             if (value == "application/mathml-presentation+xml" || value == "MathML-Presentation" || value == "image/svg+xml" || value == "SVG1.1" || value == "application/xhtml+xml" || value == "text/html")
168                 return child;
169         }
170     }
171
172     // We fallback to the first child.
173     return firstElementChild();
174 }
175
176 void MathMLSelectElement::updateSelectedChild()
177 {
178     Element* newSelectedChild = hasLocalName(mactionTag) ? getSelectedActionChild() : getSelectedSemanticsChild();
179
180     if (m_selectedChild == newSelectedChild)
181         return;
182
183     if (m_selectedChild && m_selectedChild->renderer())
184         Style::detachRenderTree(*m_selectedChild);
185
186     m_selectedChild = newSelectedChild;
187     setNeedsStyleRecalc();
188 }
189
190 void MathMLSelectElement::defaultEventHandler(Event* event)
191 {
192     if (event->type() == eventNames().clickEvent) {
193         if (fastGetAttribute(MathMLNames::actiontypeAttr) == "toggle") {
194             toggle();
195             event->setDefaultHandled();
196             return;
197         }
198     }
199
200     MathMLInlineContainerElement::defaultEventHandler(event);
201 }
202
203 bool MathMLSelectElement::willRespondToMouseClickEvents()
204 {
205     return fastGetAttribute(MathMLNames::actiontypeAttr) == "toggle";
206 }
207
208 void MathMLSelectElement::toggle()
209 {
210     // Select the successor of the currently selected child
211     // or the first child if the currently selected child is the last.
212     Element* selectedChild;
213     int newSelectedChildIndex = getSelectedActionChildAndIndex(selectedChild) + 1;
214     if (!selectedChild || !selectedChild->nextElementSibling())
215         newSelectedChildIndex = 1;
216
217     // We update the attribute value of the selection attribute.
218     // This will also call MathMLSelectElement::attributeChanged to update the selected child.
219     setAttribute(MathMLNames::selectionAttr, AtomicString::number(newSelectedChildIndex));
220 }
221
222 }
223
224 #endif // ENABLE(MATHML)