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