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