Add support for @href attribute in MathML
[WebKit-https.git] / Source / WebCore / mathml / MathMLElement.cpp
1 /*
2  * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved.
3  * Copyright (C) 2010 Apple Inc. All rights reserved.
4  * Copyright (C) 2010 Fran├žois Sausset (sausset@gmail.com). All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29
30 #if ENABLE(MATHML)
31
32 #include "MathMLElement.h"
33
34 #include "ElementIterator.h"
35 #include "Event.h"
36 #include "EventHandler.h"
37 #include "HTMLAnchorElement.h"
38 #include "HTMLElement.h"
39 #include "HTMLHtmlElement.h"
40 #include "HTMLMapElement.h"
41 #include "HTMLNames.h"
42 #include "HTMLParserIdioms.h"
43 #include "MathMLMathElement.h"
44 #include "MathMLNames.h"
45 #include "MathMLSelectElement.h"
46 #include "MouseEvent.h"
47 #include "RenderTableCell.h"
48 #include "SVGElement.h"
49 #include "SVGNames.h"
50 #include "SVGSVGElement.h"
51 #include "XLinkNames.h"
52
53 namespace WebCore {
54
55 using namespace MathMLNames;
56
57 MathMLElement::MathMLElement(const QualifiedName& tagName, Document& document)
58     : StyledElement(tagName, document, CreateMathMLElement)
59 {
60 }
61
62 Ref<MathMLElement> MathMLElement::create(const QualifiedName& tagName, Document& document)
63 {
64     return adoptRef(*new MathMLElement(tagName, document));
65 }
66
67 bool MathMLElement::isPresentationMathML() const
68 {
69     return hasTagName(MathMLNames::mtrTag)
70         || hasTagName(MathMLNames::mtdTag)
71         || hasTagName(MathMLNames::maligngroupTag)
72         || hasTagName(MathMLNames::malignmarkTag)
73         || hasTagName(MathMLNames::mencloseTag)
74         || hasTagName(MathMLNames::mglyphTag)
75         || hasTagName(MathMLNames::mlabeledtrTag)
76         || hasTagName(MathMLNames::mlongdivTag)
77         || hasTagName(MathMLNames::mpaddedTag)
78         || hasTagName(MathMLNames::msTag)
79         || hasTagName(MathMLNames::mscarriesTag)
80         || hasTagName(MathMLNames::mscarryTag)
81         || hasTagName(MathMLNames::msgroupTag)
82         || hasTagName(MathMLNames::mslineTag)
83         || hasTagName(MathMLNames::msrowTag)
84         || hasTagName(MathMLNames::mstackTag);
85 }
86
87 bool MathMLElement::isPhrasingContent(const Node& node) const
88 {
89     // Phrasing content is described in the HTML 5 specification:
90     // http://www.w3.org/TR/html5/dom.html#phrasing-content.
91
92     if (!node.isElementNode())
93         return node.isTextNode();
94
95     if (is<MathMLElement>(node)) {
96         auto& mathmlElement = downcast<MathMLElement>(node);
97         return is<MathMLMathElement>(mathmlElement);
98     }
99
100     if (is<SVGElement>(node)) {
101         auto& svgElement = downcast<SVGElement>(node);
102         return is<SVGSVGElement>(svgElement);
103     }
104
105     if (is<HTMLElement>(node)) {
106         // FIXME: add the <data> and <time> tags when they are implemented.
107         auto& htmlElement = downcast<HTMLElement>(node);
108         return htmlElement.hasTagName(HTMLNames::aTag)
109             || htmlElement.hasTagName(HTMLNames::abbrTag)
110             || (htmlElement.hasTagName(HTMLNames::areaTag) && ancestorsOfType<HTMLMapElement>(htmlElement).first())
111             || htmlElement.hasTagName(HTMLNames::audioTag)
112             || htmlElement.hasTagName(HTMLNames::bTag)
113             || htmlElement.hasTagName(HTMLNames::bdiTag)
114             || htmlElement.hasTagName(HTMLNames::bdoTag)
115             || htmlElement.hasTagName(HTMLNames::brTag)
116             || htmlElement.hasTagName(HTMLNames::buttonTag)
117             || htmlElement.hasTagName(HTMLNames::canvasTag)
118             || htmlElement.hasTagName(HTMLNames::citeTag)
119             || htmlElement.hasTagName(HTMLNames::codeTag)
120             || htmlElement.hasTagName(HTMLNames::datalistTag)
121             || htmlElement.hasTagName(HTMLNames::delTag)
122             || htmlElement.hasTagName(HTMLNames::dfnTag)
123             || htmlElement.hasTagName(HTMLNames::emTag)
124             || htmlElement.hasTagName(HTMLNames::embedTag)
125             || htmlElement.hasTagName(HTMLNames::iTag)
126             || htmlElement.hasTagName(HTMLNames::iframeTag)
127             || htmlElement.hasTagName(HTMLNames::imgTag)
128             || htmlElement.hasTagName(HTMLNames::inputTag)
129             || htmlElement.hasTagName(HTMLNames::insTag)
130             || htmlElement.hasTagName(HTMLNames::kbdTag)
131             || htmlElement.hasTagName(HTMLNames::keygenTag)
132             || htmlElement.hasTagName(HTMLNames::labelTag)
133             || htmlElement.hasTagName(HTMLNames::mapTag)
134             || htmlElement.hasTagName(HTMLNames::markTag)
135             || htmlElement.hasTagName(HTMLNames::meterTag)
136             || htmlElement.hasTagName(HTMLNames::noscriptTag)
137             || htmlElement.hasTagName(HTMLNames::objectTag)
138             || htmlElement.hasTagName(HTMLNames::outputTag)
139             || htmlElement.hasTagName(HTMLNames::progressTag)
140             || htmlElement.hasTagName(HTMLNames::qTag)
141             || htmlElement.hasTagName(HTMLNames::rubyTag)
142             || htmlElement.hasTagName(HTMLNames::sTag)
143             || htmlElement.hasTagName(HTMLNames::sampTag)
144             || htmlElement.hasTagName(HTMLNames::scriptTag)
145             || htmlElement.hasTagName(HTMLNames::selectTag)
146             || htmlElement.hasTagName(HTMLNames::smallTag)
147             || htmlElement.hasTagName(HTMLNames::spanTag)
148             || htmlElement.hasTagName(HTMLNames::strongTag)
149             || htmlElement.hasTagName(HTMLNames::subTag)
150             || htmlElement.hasTagName(HTMLNames::supTag)
151             || htmlElement.hasTagName(HTMLNames::templateTag)
152             || htmlElement.hasTagName(HTMLNames::textareaTag)
153             || htmlElement.hasTagName(HTMLNames::uTag)
154             || htmlElement.hasTagName(HTMLNames::varTag)
155             || htmlElement.hasTagName(HTMLNames::videoTag)
156             || htmlElement.hasTagName(HTMLNames::wbrTag);
157     }
158
159     return false;
160 }
161
162 bool MathMLElement::isFlowContent(const Node& node) const
163 {
164     // Flow content is described in the HTML 5 specification:
165     // http://www.w3.org/TR/html5/dom.html#flow-content
166
167     if (isPhrasingContent(node))
168         return true;
169
170     if (!is<HTMLElement>(node))
171         return false;
172
173     auto& htmlElement = downcast<HTMLElement>(node);
174     // FIXME add the <dialog> tag when it is implemented.
175     return htmlElement.hasTagName(HTMLNames::addressTag)
176         || htmlElement.hasTagName(HTMLNames::articleTag)
177         || htmlElement.hasTagName(HTMLNames::asideTag)
178         || htmlElement.hasTagName(HTMLNames::blockquoteTag)
179         || htmlElement.hasTagName(HTMLNames::detailsTag)
180         || htmlElement.hasTagName(HTMLNames::divTag)
181         || htmlElement.hasTagName(HTMLNames::dlTag)
182         || htmlElement.hasTagName(HTMLNames::fieldsetTag)
183         || htmlElement.hasTagName(HTMLNames::figureTag)
184         || htmlElement.hasTagName(HTMLNames::footerTag)
185         || htmlElement.hasTagName(HTMLNames::formTag)
186         || htmlElement.hasTagName(HTMLNames::h1Tag)
187         || htmlElement.hasTagName(HTMLNames::h2Tag)
188         || htmlElement.hasTagName(HTMLNames::h3Tag)
189         || htmlElement.hasTagName(HTMLNames::h4Tag)
190         || htmlElement.hasTagName(HTMLNames::h5Tag)
191         || htmlElement.hasTagName(HTMLNames::h6Tag)
192         || htmlElement.hasTagName(HTMLNames::headerTag)
193         || htmlElement.hasTagName(HTMLNames::hrTag)
194         || htmlElement.hasTagName(HTMLNames::mainTag)
195         || htmlElement.hasTagName(HTMLNames::navTag)
196         || htmlElement.hasTagName(HTMLNames::olTag)
197         || htmlElement.hasTagName(HTMLNames::pTag)
198         || htmlElement.hasTagName(HTMLNames::preTag)
199         || htmlElement.hasTagName(HTMLNames::sectionTag)
200         || (htmlElement.hasTagName(HTMLNames::styleTag) && htmlElement.hasAttribute("scoped"))
201         || htmlElement.hasTagName(HTMLNames::tableTag)
202         || htmlElement.hasTagName(HTMLNames::ulTag);
203 }
204
205 unsigned MathMLElement::colSpan() const
206 {
207     if (!hasTagName(mtdTag))
208         return 1u;
209     const AtomicString& colSpanValue = fastGetAttribute(columnspanAttr);
210     return std::max(1u, limitToOnlyHTMLNonNegative(colSpanValue, 1u));
211 }
212
213 unsigned MathMLElement::rowSpan() const
214 {
215     if (!hasTagName(mtdTag))
216         return 1u;
217     const AtomicString& rowSpanValue = fastGetAttribute(rowspanAttr);
218     static const unsigned maxRowspan = 8190; // This constant comes from HTMLTableCellElement.
219     return std::max(1u, std::min(limitToOnlyHTMLNonNegative(rowSpanValue, 1u), maxRowspan));
220 }
221
222 void MathMLElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
223 {
224     if (name == hrefAttr) {
225         bool wasLink = isLink();
226         setIsLink(!value.isNull() && !shouldProhibitLinks(this));
227         if (wasLink != isLink())
228             setNeedsStyleRecalc();
229     } else if (name == rowspanAttr) {
230         if (is<RenderTableCell>(renderer()) && hasTagName(mtdTag))
231             downcast<RenderTableCell>(*renderer()).colSpanOrRowSpanChanged();
232     } else if (name == columnspanAttr) {
233         if (is<RenderTableCell>(renderer()) && hasTagName(mtdTag))
234             downcast<RenderTableCell>(renderer())->colSpanOrRowSpanChanged();
235     } else
236         StyledElement::parseAttribute(name, value);
237 }
238
239 bool MathMLElement::isPresentationAttribute(const QualifiedName& name) const
240 {
241     if (name == backgroundAttr || name == colorAttr || name == dirAttr || name == fontfamilyAttr || name == fontsizeAttr || name == fontstyleAttr || name == fontweightAttr || name == mathbackgroundAttr || name == mathcolorAttr || name == mathsizeAttr)
242         return true;
243     return StyledElement::isPresentationAttribute(name);
244 }
245
246 void MathMLElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
247 {
248     if (name == mathbackgroundAttr)
249         addPropertyToPresentationAttributeStyle(style, CSSPropertyBackgroundColor, value);
250     else if (name == mathsizeAttr) {
251         // The following three values of mathsize are handled in WebCore/css/mathml.css
252         if (value != "normal" && value != "small" && value != "big")
253             addPropertyToPresentationAttributeStyle(style, CSSPropertyFontSize, value);
254     } else if (name == mathcolorAttr)
255         addPropertyToPresentationAttributeStyle(style, CSSPropertyColor, value);
256     // FIXME: deprecated attributes that should loose in a conflict with a non deprecated attribute
257     else if (name == fontsizeAttr)
258         addPropertyToPresentationAttributeStyle(style, CSSPropertyFontSize, value);
259     else if (name == backgroundAttr)
260         addPropertyToPresentationAttributeStyle(style, CSSPropertyBackgroundColor, value);
261     else if (name == colorAttr)
262         addPropertyToPresentationAttributeStyle(style, CSSPropertyColor, value);
263     else if (name == fontstyleAttr)
264         addPropertyToPresentationAttributeStyle(style, CSSPropertyFontStyle, value);
265     else if (name == fontweightAttr)
266         addPropertyToPresentationAttributeStyle(style, CSSPropertyFontWeight, value);
267     else if (name == fontfamilyAttr)
268         addPropertyToPresentationAttributeStyle(style, CSSPropertyFontFamily, value);
269     else if (name == dirAttr) {
270         if (hasTagName(mathTag) || hasTagName(mrowTag) || hasTagName(mstyleTag) || isMathMLToken())
271             addPropertyToPresentationAttributeStyle(style, CSSPropertyDirection, value);
272     }  else {
273         ASSERT(!isPresentationAttribute(name));
274         StyledElement::collectStyleForPresentationAttribute(name, value
275         , style);
276     }
277 }
278
279 bool MathMLElement::childShouldCreateRenderer(const Node& child) const
280 {
281     if (hasTagName(annotation_xmlTag)) {
282         const AtomicString& value = fastGetAttribute(MathMLNames::encodingAttr);
283
284         // See annotation-xml.model.mathml, annotation-xml.model.svg and annotation-xml.model.xhtml in the HTML5 RelaxNG schema.
285
286         if (is<MathMLElement>(child) && (MathMLSelectElement::isMathMLEncoding(value) || MathMLSelectElement::isHTMLEncoding(value))) {
287             auto& mathmlElement = downcast<MathMLElement>(child);
288             return is<MathMLMathElement>(mathmlElement);
289         }
290
291         if (is<SVGElement>(child) && (MathMLSelectElement::isSVGEncoding(value) || MathMLSelectElement::isHTMLEncoding(value))) {
292             auto& svgElement = downcast<SVGElement>(child);
293             return is<SVGSVGElement>(svgElement);
294         }
295
296         if (is<HTMLElement>(child) && MathMLSelectElement::isHTMLEncoding(value)) {
297             auto& htmlElement = downcast<HTMLElement>(child);
298             return is<HTMLHtmlElement>(htmlElement) || (isFlowContent(htmlElement) && StyledElement::childShouldCreateRenderer(child));
299         }
300
301         return false;
302     }
303
304     // In general, only MathML children are allowed. Text nodes are only visible in token MathML elements.
305     return is<MathMLElement>(child);
306 }
307
308 void MathMLElement::attributeChanged(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue, AttributeModificationReason reason)
309 {
310     if (isSemanticAnnotation() && (name == MathMLNames::srcAttr || name == MathMLNames::encodingAttr)) {
311         auto* parent = parentElement();
312         if (is<MathMLElement>(parent) && parent->hasTagName(semanticsTag))
313             downcast<MathMLElement>(*parent).updateSelectedChild();
314     }
315     StyledElement::attributeChanged(name, oldValue, newValue, reason);
316 }
317
318 bool MathMLElement::willRespondToMouseClickEvents()
319 {
320     return isLink() || StyledElement::willRespondToMouseClickEvents();
321 }
322
323 void MathMLElement::defaultEventHandler(Event* event)
324 {
325     if (isLink()) {
326         if (focused() && isEnterKeyKeydownEvent(event)) {
327             event->setDefaultHandled();
328             dispatchSimulatedClick(event);
329             return;
330         }
331         if (MouseEvent::canTriggerActivationBehavior(*event)) {
332             const AtomicString& href = fastGetAttribute(hrefAttr);
333             String url = stripLeadingAndTrailingHTMLSpaces(href);
334             event->setDefaultHandled();
335             if (Frame* frame = document().frame())
336                 frame->loader().urlSelected(document().completeURL(url), "_self", event, LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate());
337             return;
338         }
339     }
340
341     StyledElement::defaultEventHandler(event);
342 }
343
344 bool MathMLElement::canStartSelection() const
345 {
346     if (!isLink())
347         return StyledElement::canStartSelection();
348
349     return hasEditableStyle();
350 }
351
352 bool MathMLElement::isFocusable() const
353 {
354     if (renderer() && renderer()->absoluteClippedOverflowRect().isEmpty())
355         return false;
356
357     return StyledElement::isFocusable();
358 }
359
360 bool MathMLElement::isKeyboardFocusable(KeyboardEvent* event) const
361 {
362     if (isFocusable() && StyledElement::supportsFocus())
363         return StyledElement::isKeyboardFocusable(event);
364
365     if (isLink())
366         return document().frame()->eventHandler().tabsToLinks(event);
367
368     return StyledElement::isKeyboardFocusable(event);
369 }
370
371 bool MathMLElement::isMouseFocusable() const
372 {
373     // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable.
374     // https://bugs.webkit.org/show_bug.cgi?id=26856
375     if (isLink())
376         return StyledElement::supportsFocus();
377
378     return StyledElement::isMouseFocusable();
379 }
380
381 bool MathMLElement::isURLAttribute(const Attribute& attribute) const
382 {
383     return attribute.name().localName() == hrefAttr || StyledElement::isURLAttribute(attribute);
384 }
385
386 bool MathMLElement::supportsFocus() const
387 {
388     if (hasEditableStyle())
389         return StyledElement::supportsFocus();
390     // If not a link we should still be able to focus the element if it has tabIndex.
391     return isLink() || StyledElement::supportsFocus();
392 }
393
394 int MathMLElement::tabIndex() const
395 {
396     // Skip the supportsFocus check in StyledElement.
397     return Element::tabIndex();
398 }
399
400 }
401
402 #endif // ENABLE(MATHML)