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