2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2004-2008, 2013, 2014 Apple Inc. All rights reserved.
5 * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
6 * Copyright (C) 2011 Motorola Mobility. All rights reserved.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
26 #include "core/html/HTMLElement.h"
28 #include "bindings/core/v8/ExceptionState.h"
29 #include "bindings/core/v8/ScriptEventListener.h"
30 #include "core/CSSPropertyNames.h"
31 #include "core/CSSValueKeywords.h"
32 #include "core/HTMLNames.h"
33 #include "core/XMLNames.h"
34 #include "core/css/CSSMarkup.h"
35 #include "core/css/CSSValuePool.h"
36 #include "core/css/StylePropertySet.h"
37 #include "core/dom/DocumentFragment.h"
38 #include "core/dom/ElementTraversal.h"
39 #include "core/dom/ExceptionCode.h"
40 #include "core/dom/NodeTraversal.h"
41 #include "core/dom/Text.h"
42 #include "core/dom/shadow/ComposedTreeTraversal.h"
43 #include "core/dom/shadow/ElementShadow.h"
44 #include "core/dom/shadow/ShadowRoot.h"
45 #include "core/editing/serializers/Serialization.h"
46 #include "core/events/EventListener.h"
47 #include "core/events/KeyboardEvent.h"
48 #include "core/frame/Settings.h"
49 #include "core/frame/UseCounter.h"
50 #include "core/html/HTMLBRElement.h"
51 #include "core/html/HTMLFormElement.h"
52 #include "core/html/HTMLInputElement.h"
53 #include "core/html/HTMLMenuElement.h"
54 #include "core/html/HTMLTemplateElement.h"
55 #include "core/html/HTMLTextFormControlElement.h"
56 #include "core/html/parser/HTMLParserIdioms.h"
57 #include "core/layout/LayoutObject.h"
58 #include "core/page/SpatialNavigation.h"
59 #include "platform/Language.h"
60 #include "platform/text/BidiResolver.h"
61 #include "platform/text/BidiTextRun.h"
62 #include "platform/text/TextRunIterator.h"
63 #include "wtf/StdLibExtras.h"
64 #include "wtf/text/CString.h"
68 using namespace HTMLNames
;
73 DEFINE_ELEMENT_FACTORY_WITH_TAGNAME(HTMLElement
);
75 String
HTMLElement::nodeName() const
77 // FIXME: Would be nice to have an atomicstring lookup based off uppercase
78 // chars that does not have to copy the string on a hit in the hash.
79 // FIXME: We should have a way to detect XHTML elements and replace the hasPrefix() check with it.
80 if (document().isHTMLDocument()) {
81 if (!tagQName().hasPrefix())
82 return tagQName().localNameUpper();
83 return Element::nodeName().upper();
85 return Element::nodeName();
88 bool HTMLElement::ieForbidsInsertHTML() const
90 // FIXME: Supposedly IE disallows settting innerHTML, outerHTML
91 // and createContextualFragment on these tags. We have no tests to
92 // verify this however, so this list could be totally wrong.
93 // This list was moved from the previous endTagRequirement() implementation.
94 // This is also called from editing and assumed to be the list of tags
95 // for which no end tag should be serialized. It's unclear if the list for
96 // IE compat and the list for serialization sanity are the same.
97 if (hasTagName(areaTag
)
98 || hasTagName(baseTag
)
99 || hasTagName(basefontTag
)
101 || hasTagName(colTag
)
102 || hasTagName(embedTag
)
103 || hasTagName(frameTag
)
105 || hasTagName(imageTag
)
106 || hasTagName(imgTag
)
107 || hasTagName(inputTag
)
108 || hasTagName(linkTag
)
109 || (RuntimeEnabledFeatures::contextMenuEnabled() && hasTagName(menuitemTag
))
110 || hasTagName(metaTag
)
111 || hasTagName(paramTag
)
112 || hasTagName(sourceTag
)
113 || hasTagName(wbrTag
))
118 static inline CSSValueID
unicodeBidiAttributeForDirAuto(HTMLElement
* element
)
120 if (element
->hasTagName(preTag
) || element
->hasTagName(textareaTag
))
121 return CSSValueWebkitPlaintext
;
122 // FIXME: For bdo element, dir="auto" should result in "bidi-override isolate" but we don't support having multiple values in unicode-bidi yet.
123 // See https://bugs.webkit.org/show_bug.cgi?id=73164.
124 return CSSValueWebkitIsolate
;
127 unsigned HTMLElement::parseBorderWidthAttribute(const AtomicString
& value
) const
129 unsigned borderWidth
= 0;
130 if (value
.isEmpty() || !parseHTMLNonNegativeInteger(value
, borderWidth
)) {
131 if (hasTagName(tableTag
) && !value
.isNull())
137 void HTMLElement::applyBorderAttributeToStyle(const AtomicString
& value
, MutableStylePropertySet
* style
)
139 addPropertyToPresentationAttributeStyle(style
, CSSPropertyBorderWidth
, parseBorderWidthAttribute(value
), CSSPrimitiveValue::UnitType::Pixels
);
140 addPropertyToPresentationAttributeStyle(style
, CSSPropertyBorderStyle
, CSSValueSolid
);
143 void HTMLElement::mapLanguageAttributeToLocale(const AtomicString
& value
, MutableStylePropertySet
* style
)
145 if (!value
.isEmpty()) {
146 // Have to quote so the locale id is treated as a string instead of as a CSS keyword.
147 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitLocale
, serializeString(value
));
149 // FIXME: Remove the following UseCounter code when we collect enough
151 UseCounter::count(document(), UseCounter::LangAttribute
);
152 if (isHTMLHtmlElement(*this))
153 UseCounter::count(document(), UseCounter::LangAttributeOnHTML
);
154 else if (isHTMLBodyElement(*this))
155 UseCounter::count(document(), UseCounter::LangAttributeOnBody
);
156 String htmlLanguage
= value
.string();
157 size_t firstSeparator
= htmlLanguage
.find('-');
158 if (firstSeparator
!= kNotFound
)
159 htmlLanguage
= htmlLanguage
.left(firstSeparator
);
160 String uiLanguage
= defaultLanguage();
161 firstSeparator
= uiLanguage
.find('-');
162 if (firstSeparator
!= kNotFound
)
163 uiLanguage
= uiLanguage
.left(firstSeparator
);
164 firstSeparator
= uiLanguage
.find('_');
165 if (firstSeparator
!= kNotFound
)
166 uiLanguage
= uiLanguage
.left(firstSeparator
);
167 if (!equalIgnoringCase(htmlLanguage
, uiLanguage
))
168 UseCounter::count(document(), UseCounter::LangAttributeDoesNotMatchToUILocale
);
170 // The empty string means the language is explicitly unknown.
171 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitLocale
, CSSValueAuto
);
175 bool HTMLElement::isPresentationAttribute(const QualifiedName
& name
) const
177 if (name
== alignAttr
|| name
== contenteditableAttr
|| name
== hiddenAttr
|| name
== langAttr
|| name
.matches(XMLNames::langAttr
) || name
== draggableAttr
|| name
== dirAttr
)
179 return Element::isPresentationAttribute(name
);
182 static inline bool isValidDirAttribute(const AtomicString
& value
)
184 return equalIgnoringCase(value
, "auto") || equalIgnoringCase(value
, "ltr") || equalIgnoringCase(value
, "rtl");
187 void HTMLElement::collectStyleForPresentationAttribute(const QualifiedName
& name
, const AtomicString
& value
, MutableStylePropertySet
* style
)
189 if (name
== alignAttr
) {
190 if (equalIgnoringCase(value
, "middle"))
191 addPropertyToPresentationAttributeStyle(style
, CSSPropertyTextAlign
, CSSValueCenter
);
193 addPropertyToPresentationAttributeStyle(style
, CSSPropertyTextAlign
, value
);
194 } else if (name
== contenteditableAttr
) {
195 if (value
.isEmpty() || equalIgnoringCase(value
, "true")) {
196 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitUserModify
, CSSValueReadWrite
);
197 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWordWrap
, CSSValueBreakWord
);
198 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitLineBreak
, CSSValueAfterWhiteSpace
);
199 } else if (equalIgnoringCase(value
, "plaintext-only")) {
200 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitUserModify
, CSSValueReadWritePlaintextOnly
);
201 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWordWrap
, CSSValueBreakWord
);
202 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitLineBreak
, CSSValueAfterWhiteSpace
);
203 } else if (equalIgnoringCase(value
, "false")) {
204 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitUserModify
, CSSValueReadOnly
);
206 } else if (name
== hiddenAttr
) {
207 addPropertyToPresentationAttributeStyle(style
, CSSPropertyDisplay
, CSSValueNone
);
208 } else if (name
== draggableAttr
) {
209 if (equalIgnoringCase(value
, "true")) {
210 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitUserDrag
, CSSValueElement
);
211 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitUserSelect
, CSSValueNone
);
212 } else if (equalIgnoringCase(value
, "false")) {
213 addPropertyToPresentationAttributeStyle(style
, CSSPropertyWebkitUserDrag
, CSSValueNone
);
215 } else if (name
== dirAttr
) {
216 if (equalIgnoringCase(value
, "auto")) {
217 addPropertyToPresentationAttributeStyle(style
, CSSPropertyUnicodeBidi
, unicodeBidiAttributeForDirAuto(this));
219 if (isValidDirAttribute(value
))
220 addPropertyToPresentationAttributeStyle(style
, CSSPropertyDirection
, value
);
222 addPropertyToPresentationAttributeStyle(style
, CSSPropertyDirection
, "ltr");
223 if (!hasTagName(bdiTag
) && !hasTagName(bdoTag
) && !hasTagName(outputTag
))
224 addPropertyToPresentationAttributeStyle(style
, CSSPropertyUnicodeBidi
, CSSValueEmbed
);
226 } else if (name
.matches(XMLNames::langAttr
)) {
227 mapLanguageAttributeToLocale(value
, style
);
228 } else if (name
== langAttr
) {
229 // xml:lang has a higher priority than lang.
230 if (!fastHasAttribute(XMLNames::langAttr
))
231 mapLanguageAttributeToLocale(value
, style
);
233 Element::collectStyleForPresentationAttribute(name
, value
, style
);
237 const AtomicString
& HTMLElement::eventNameForAttributeName(const QualifiedName
& attrName
)
239 if (!attrName
.namespaceURI().isNull())
242 if (!attrName
.localName().startsWith("on", TextCaseInsensitive
))
245 typedef HashMap
<AtomicString
, AtomicString
> StringToStringMap
;
246 DEFINE_STATIC_LOCAL(StringToStringMap
, attributeNameToEventNameMap
, ());
247 if (!attributeNameToEventNameMap
.size()) {
248 struct AttrToEventName
{
249 const QualifiedName
& attr
;
250 const AtomicString
& event
;
252 AttrToEventName attrToEventNames
[] = {
253 { onabortAttr
, EventTypeNames::abort
},
254 { onanimationendAttr
, EventTypeNames::animationend
},
255 { onanimationiterationAttr
, EventTypeNames::animationiteration
},
256 { onanimationstartAttr
, EventTypeNames::animationstart
},
257 { onautocompleteAttr
, EventTypeNames::autocomplete
},
258 { onautocompleteerrorAttr
, EventTypeNames::autocompleteerror
},
259 { onbeforecopyAttr
, EventTypeNames::beforecopy
},
260 { onbeforecutAttr
, EventTypeNames::beforecut
},
261 { onbeforepasteAttr
, EventTypeNames::beforepaste
},
262 { onblurAttr
, EventTypeNames::blur
},
263 { oncancelAttr
, EventTypeNames::cancel
},
264 { oncanplayAttr
, EventTypeNames::canplay
},
265 { oncanplaythroughAttr
, EventTypeNames::canplaythrough
},
266 { onchangeAttr
, EventTypeNames::change
},
267 { onclickAttr
, EventTypeNames::click
},
268 { oncloseAttr
, EventTypeNames::close
},
269 { oncontextmenuAttr
, EventTypeNames::contextmenu
},
270 { oncopyAttr
, EventTypeNames::copy
},
271 { oncuechangeAttr
, EventTypeNames::cuechange
},
272 { oncutAttr
, EventTypeNames::cut
},
273 { ondblclickAttr
, EventTypeNames::dblclick
},
274 { ondragAttr
, EventTypeNames::drag
},
275 { ondragendAttr
, EventTypeNames::dragend
},
276 { ondragenterAttr
, EventTypeNames::dragenter
},
277 { ondragleaveAttr
, EventTypeNames::dragleave
},
278 { ondragoverAttr
, EventTypeNames::dragover
},
279 { ondragstartAttr
, EventTypeNames::dragstart
},
280 { ondropAttr
, EventTypeNames::drop
},
281 { ondurationchangeAttr
, EventTypeNames::durationchange
},
282 { onemptiedAttr
, EventTypeNames::emptied
},
283 { onendedAttr
, EventTypeNames::ended
},
284 { onerrorAttr
, EventTypeNames::error
},
285 { onfocusAttr
, EventTypeNames::focus
},
286 { onfocusinAttr
, EventTypeNames::focusin
},
287 { onfocusoutAttr
, EventTypeNames::focusout
},
288 { oninputAttr
, EventTypeNames::input
},
289 { oninvalidAttr
, EventTypeNames::invalid
},
290 { onkeydownAttr
, EventTypeNames::keydown
},
291 { onkeypressAttr
, EventTypeNames::keypress
},
292 { onkeyupAttr
, EventTypeNames::keyup
},
293 { onloadAttr
, EventTypeNames::load
},
294 { onloadeddataAttr
, EventTypeNames::loadeddata
},
295 { onloadedmetadataAttr
, EventTypeNames::loadedmetadata
},
296 { onloadstartAttr
, EventTypeNames::loadstart
},
297 { onmousedownAttr
, EventTypeNames::mousedown
},
298 { onmouseenterAttr
, EventTypeNames::mouseenter
},
299 { onmouseleaveAttr
, EventTypeNames::mouseleave
},
300 { onmousemoveAttr
, EventTypeNames::mousemove
},
301 { onmouseoutAttr
, EventTypeNames::mouseout
},
302 { onmouseoverAttr
, EventTypeNames::mouseover
},
303 { onmouseupAttr
, EventTypeNames::mouseup
},
304 { onmousewheelAttr
, EventTypeNames::mousewheel
},
305 { onpasteAttr
, EventTypeNames::paste
},
306 { onpauseAttr
, EventTypeNames::pause
},
307 { onplayAttr
, EventTypeNames::play
},
308 { onplayingAttr
, EventTypeNames::playing
},
309 { onprogressAttr
, EventTypeNames::progress
},
310 { onratechangeAttr
, EventTypeNames::ratechange
},
311 { onresetAttr
, EventTypeNames::reset
},
312 { onresizeAttr
, EventTypeNames::resize
},
313 { onscrollAttr
, EventTypeNames::scroll
},
314 { onseekedAttr
, EventTypeNames::seeked
},
315 { onseekingAttr
, EventTypeNames::seeking
},
316 { onselectAttr
, EventTypeNames::select
},
317 { onselectstartAttr
, EventTypeNames::selectstart
},
318 { onshowAttr
, EventTypeNames::show
},
319 { onstalledAttr
, EventTypeNames::stalled
},
320 { onsubmitAttr
, EventTypeNames::submit
},
321 { onsuspendAttr
, EventTypeNames::suspend
},
322 { ontimeupdateAttr
, EventTypeNames::timeupdate
},
323 { ontoggleAttr
, EventTypeNames::toggle
},
324 { ontouchcancelAttr
, EventTypeNames::touchcancel
},
325 { ontouchendAttr
, EventTypeNames::touchend
},
326 { ontouchmoveAttr
, EventTypeNames::touchmove
},
327 { ontouchstartAttr
, EventTypeNames::touchstart
},
328 { ontransitionendAttr
, EventTypeNames::webkitTransitionEnd
},
329 { onvolumechangeAttr
, EventTypeNames::volumechange
},
330 { onwaitingAttr
, EventTypeNames::waiting
},
331 { onwebkitanimationendAttr
, EventTypeNames::webkitAnimationEnd
},
332 { onwebkitanimationiterationAttr
, EventTypeNames::webkitAnimationIteration
},
333 { onwebkitanimationstartAttr
, EventTypeNames::webkitAnimationStart
},
334 { onwebkitfullscreenchangeAttr
, EventTypeNames::webkitfullscreenchange
},
335 { onwebkitfullscreenerrorAttr
, EventTypeNames::webkitfullscreenerror
},
336 { onwebkittransitionendAttr
, EventTypeNames::webkitTransitionEnd
},
337 { onwheelAttr
, EventTypeNames::wheel
},
340 for (size_t i
= 0; i
< WTF_ARRAY_LENGTH(attrToEventNames
); i
++)
341 attributeNameToEventNameMap
.set(attrToEventNames
[i
].attr
.localName(), attrToEventNames
[i
].event
);
344 return attributeNameToEventNameMap
.get(attrName
.localName());
347 void HTMLElement::parseAttribute(const QualifiedName
& name
, const AtomicString
& value
)
349 if (name
== tabindexAttr
)
350 return Element::parseAttribute(name
, value
);
352 if (name
== dirAttr
) {
353 dirAttributeChanged(value
);
355 const AtomicString
& eventName
= eventNameForAttributeName(name
);
356 if (!eventName
.isNull())
357 setAttributeEventListener(eventName
, createAttributeEventListener(this, name
, value
, eventParameterName()));
361 PassRefPtrWillBeRawPtr
<DocumentFragment
> HTMLElement::textToFragment(const String
& text
, ExceptionState
& exceptionState
)
363 RefPtrWillBeRawPtr
<DocumentFragment
> fragment
= DocumentFragment::create(document());
364 unsigned i
, length
= text
.length();
366 for (unsigned start
= 0; start
< length
; ) {
368 // Find next line break.
369 for (i
= start
; i
< length
; i
++) {
371 if (c
== '\r' || c
== '\n')
375 fragment
->appendChild(Text::create(document(), text
.substring(start
, i
- start
)), exceptionState
);
376 if (exceptionState
.hadException())
379 if (c
== '\r' || c
== '\n') {
380 fragment
->appendChild(HTMLBRElement::create(document()), exceptionState
);
381 if (exceptionState
.hadException())
383 // Make sure \r\n doesn't result in two line breaks.
384 if (c
== '\r' && i
+ 1 < length
&& text
[i
+ 1] == '\n')
388 start
= i
+ 1; // Character after line break.
394 static inline bool shouldProhibitSetInnerOuterText(const HTMLElement
& element
)
396 return element
.hasTagName(colTag
)
397 || element
.hasTagName(colgroupTag
)
398 || element
.hasTagName(framesetTag
)
399 || element
.hasTagName(headTag
)
400 || element
.hasTagName(htmlTag
)
401 || element
.hasTagName(tableTag
)
402 || element
.hasTagName(tbodyTag
)
403 || element
.hasTagName(tfootTag
)
404 || element
.hasTagName(theadTag
)
405 || element
.hasTagName(trTag
);
408 void HTMLElement::setInnerText(const String
& text
, ExceptionState
& exceptionState
)
410 if (ieForbidsInsertHTML()) {
411 exceptionState
.throwDOMException(NoModificationAllowedError
, "The '" + localName() + "' element does not support text insertion.");
414 if (shouldProhibitSetInnerOuterText(*this)) {
415 exceptionState
.throwDOMException(NoModificationAllowedError
, "The '" + localName() + "' element does not support text insertion.");
419 // FIXME: This doesn't take whitespace collapsing into account at all.
421 if (!text
.contains('\n') && !text
.contains('\r')) {
422 if (text
.isEmpty()) {
426 replaceChildrenWithText(this, text
, exceptionState
);
430 // FIXME: Do we need to be able to detect preserveNewline style even when there's no layoutObject?
431 // FIXME: Can the layoutObject be out of date here? Do we need to call updateStyleIfNeeded?
432 // For example, for the contents of textarea elements that are display:none?
433 LayoutObject
* r
= layoutObject();
434 if (r
&& r
->style()->preserveNewline()) {
435 if (!text
.contains('\r')) {
436 replaceChildrenWithText(this, text
, exceptionState
);
439 String textWithConsistentLineBreaks
= text
;
440 textWithConsistentLineBreaks
.replace("\r\n", "\n");
441 textWithConsistentLineBreaks
.replace('\r', '\n');
442 replaceChildrenWithText(this, textWithConsistentLineBreaks
, exceptionState
);
446 // Add text nodes and <br> elements.
447 RefPtrWillBeRawPtr
<DocumentFragment
> fragment
= textToFragment(text
, exceptionState
);
448 if (!exceptionState
.hadException())
449 replaceChildrenWithFragment(this, fragment
.release(), exceptionState
);
452 void HTMLElement::setOuterText(const String
& text
, ExceptionState
& exceptionState
)
454 if (ieForbidsInsertHTML()) {
455 exceptionState
.throwDOMException(NoModificationAllowedError
, "The '" + localName() + "' element does not support text insertion.");
458 if (shouldProhibitSetInnerOuterText(*this)) {
459 exceptionState
.throwDOMException(NoModificationAllowedError
, "The '" + localName() + "' element does not support text insertion.");
463 ContainerNode
* parent
= parentNode();
465 exceptionState
.throwDOMException(NoModificationAllowedError
, "The element has no parent.");
469 RefPtrWillBeRawPtr
<Node
> prev
= previousSibling();
470 RefPtrWillBeRawPtr
<Node
> next
= nextSibling();
471 RefPtrWillBeRawPtr
<Node
> newChild
= nullptr;
473 // Convert text to fragment with <br> tags instead of linebreaks if needed.
474 if (text
.contains('\r') || text
.contains('\n'))
475 newChild
= textToFragment(text
, exceptionState
);
477 newChild
= Text::create(document(), text
);
479 // textToFragment might cause mutation events.
481 exceptionState
.throwDOMException(HierarchyRequestError
, "The element has no parent.");
483 if (exceptionState
.hadException())
486 parent
->replaceChild(newChild
.release(), this, exceptionState
);
488 RefPtrWillBeRawPtr
<Node
> node
= next
? next
->previousSibling() : nullptr;
489 if (!exceptionState
.hadException() && node
&& node
->isTextNode())
490 mergeWithNextTextNode(toText(node
.get()), exceptionState
);
492 if (!exceptionState
.hadException() && prev
&& prev
->isTextNode())
493 mergeWithNextTextNode(toText(prev
.get()), exceptionState
);
496 void HTMLElement::applyAlignmentAttributeToStyle(const AtomicString
& alignment
, MutableStylePropertySet
* style
)
498 // Vertical alignment with respect to the current baseline of the text
499 // right or left means floating images.
500 CSSValueID floatValue
= CSSValueInvalid
;
501 CSSValueID verticalAlignValue
= CSSValueInvalid
;
503 if (equalIgnoringCase(alignment
, "absmiddle")) {
504 verticalAlignValue
= CSSValueMiddle
;
505 } else if (equalIgnoringCase(alignment
, "absbottom")) {
506 verticalAlignValue
= CSSValueBottom
;
507 } else if (equalIgnoringCase(alignment
, "left")) {
508 floatValue
= CSSValueLeft
;
509 verticalAlignValue
= CSSValueTop
;
510 } else if (equalIgnoringCase(alignment
, "right")) {
511 floatValue
= CSSValueRight
;
512 verticalAlignValue
= CSSValueTop
;
513 } else if (equalIgnoringCase(alignment
, "top")) {
514 verticalAlignValue
= CSSValueTop
;
515 } else if (equalIgnoringCase(alignment
, "middle")) {
516 verticalAlignValue
= CSSValueWebkitBaselineMiddle
;
517 } else if (equalIgnoringCase(alignment
, "center")) {
518 verticalAlignValue
= CSSValueMiddle
;
519 } else if (equalIgnoringCase(alignment
, "bottom")) {
520 verticalAlignValue
= CSSValueBaseline
;
521 } else if (equalIgnoringCase(alignment
, "texttop")) {
522 verticalAlignValue
= CSSValueTextTop
;
525 if (floatValue
!= CSSValueInvalid
)
526 addPropertyToPresentationAttributeStyle(style
, CSSPropertyFloat
, floatValue
);
528 if (verticalAlignValue
!= CSSValueInvalid
)
529 addPropertyToPresentationAttributeStyle(style
, CSSPropertyVerticalAlign
, verticalAlignValue
);
532 bool HTMLElement::hasCustomFocusLogic() const
537 String
HTMLElement::contentEditable() const
539 const AtomicString
& value
= fastGetAttribute(contenteditableAttr
);
543 if (value
.isEmpty() || equalIgnoringCase(value
, "true"))
545 if (equalIgnoringCase(value
, "false"))
547 if (equalIgnoringCase(value
, "plaintext-only"))
548 return "plaintext-only";
553 void HTMLElement::setContentEditable(const String
& enabled
, ExceptionState
& exceptionState
)
555 if (equalIgnoringCase(enabled
, "true"))
556 setAttribute(contenteditableAttr
, "true");
557 else if (equalIgnoringCase(enabled
, "false"))
558 setAttribute(contenteditableAttr
, "false");
559 else if (equalIgnoringCase(enabled
, "plaintext-only"))
560 setAttribute(contenteditableAttr
, "plaintext-only");
561 else if (equalIgnoringCase(enabled
, "inherit"))
562 removeAttribute(contenteditableAttr
);
564 exceptionState
.throwDOMException(SyntaxError
, "The value provided ('" + enabled
+ "') is not one of 'true', 'false', 'plaintext-only', or 'inherit'.");
567 bool HTMLElement::draggable() const
569 return equalIgnoringCase(getAttribute(draggableAttr
), "true");
572 void HTMLElement::setDraggable(bool value
)
574 setAttribute(draggableAttr
, value
? "true" : "false");
577 bool HTMLElement::spellcheck() const
579 return isSpellCheckingEnabled();
582 void HTMLElement::setSpellcheck(bool enable
)
584 setAttribute(spellcheckAttr
, enable
? "true" : "false");
587 void HTMLElement::clickForBindings()
589 dispatchSimulatedClick(0, SendNoEvents
, SimulatedClickCreationScope::FromScript
);
592 void HTMLElement::accessKeyAction(bool sendMouseEvents
)
594 dispatchSimulatedClick(0, sendMouseEvents
? SendMouseUpDownEvents
: SendNoEvents
);
597 String
HTMLElement::title() const
599 return fastGetAttribute(titleAttr
);
602 short HTMLElement::tabIndex() const
605 return Element::tabIndex();
609 TranslateAttributeMode
HTMLElement::translateAttributeMode() const
611 const AtomicString
& value
= getAttribute(translateAttr
);
613 if (value
== nullAtom
)
614 return TranslateAttributeInherit
;
615 if (equalIgnoringCase(value
, "yes") || equalIgnoringCase(value
, ""))
616 return TranslateAttributeYes
;
617 if (equalIgnoringCase(value
, "no"))
618 return TranslateAttributeNo
;
620 return TranslateAttributeInherit
;
623 bool HTMLElement::translate() const
625 for (const HTMLElement
* element
= this; element
; element
= Traversal
<HTMLElement
>::firstAncestor(*element
)) {
626 TranslateAttributeMode mode
= element
->translateAttributeMode();
627 if (mode
!= TranslateAttributeInherit
) {
628 ASSERT(mode
== TranslateAttributeYes
|| mode
== TranslateAttributeNo
);
629 return mode
== TranslateAttributeYes
;
633 // Default on the root element is translate=yes.
637 void HTMLElement::setTranslate(bool enable
)
639 setAttribute(translateAttr
, enable
? "yes" : "no");
642 // Returns the conforming 'dir' value associated with the state the attribute is in (in its canonical case), if any,
643 // or the empty string if the attribute is in a state that has no associated keyword value or if the attribute is
644 // not in a defined state (e.g. the attribute is missing and there is no missing value default).
645 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#limited-to-only-known-values
646 static inline const AtomicString
& toValidDirValue(const AtomicString
& value
)
648 DEFINE_STATIC_LOCAL(const AtomicString
, ltrValue
, ("ltr", AtomicString::ConstructFromLiteral
));
649 DEFINE_STATIC_LOCAL(const AtomicString
, rtlValue
, ("rtl", AtomicString::ConstructFromLiteral
));
650 DEFINE_STATIC_LOCAL(const AtomicString
, autoValue
, ("auto", AtomicString::ConstructFromLiteral
));
652 if (equalIgnoringCase(value
, ltrValue
))
654 if (equalIgnoringCase(value
, rtlValue
))
656 if (equalIgnoringCase(value
, autoValue
))
661 const AtomicString
& HTMLElement::dir()
663 return toValidDirValue(fastGetAttribute(dirAttr
));
666 void HTMLElement::setDir(const AtomicString
& value
)
668 setAttribute(dirAttr
, value
);
671 HTMLFormElement
* HTMLElement::findFormAncestor() const
673 return Traversal
<HTMLFormElement
>::firstAncestor(*this);
676 static inline bool elementAffectsDirectionality(const Node
* node
)
678 return node
->isHTMLElement() && (isHTMLBDIElement(toHTMLElement(*node
)) || toHTMLElement(*node
).hasAttribute(dirAttr
));
681 void HTMLElement::childrenChanged(const ChildrenChange
& change
)
683 Element::childrenChanged(change
);
684 adjustDirectionalityIfNeededAfterChildrenChanged(change
);
687 bool HTMLElement::hasDirectionAuto() const
689 // <bdi> defaults to dir="auto"
690 // https://html.spec.whatwg.org/multipage/semantics.html#the-bdi-element
691 const AtomicString
& direction
= fastGetAttribute(dirAttr
);
692 return (isHTMLBDIElement(*this) && direction
== nullAtom
) || equalIgnoringCase(direction
, "auto");
695 TextDirection
HTMLElement::directionalityIfhasDirAutoAttribute(bool& isAuto
) const
697 isAuto
= hasDirectionAuto();
700 return directionality();
703 TextDirection
HTMLElement::directionality(Node
** strongDirectionalityTextNode
) const
705 if (isHTMLInputElement(*this)) {
706 HTMLInputElement
* inputElement
= toHTMLInputElement(const_cast<HTMLElement
*>(this));
707 bool hasStrongDirectionality
;
708 TextDirection textDirection
= determineDirectionality(inputElement
->value(), &hasStrongDirectionality
);
709 if (strongDirectionalityTextNode
)
710 *strongDirectionalityTextNode
= hasStrongDirectionality
? inputElement
: 0;
711 return textDirection
;
714 Node
* node
= ComposedTreeTraversal::firstChild(*this);
716 // Skip bdi, script, style and text form controls.
717 if (equalIgnoringCase(node
->nodeName(), "bdi") || isHTMLScriptElement(*node
) || isHTMLStyleElement(*node
)
718 || (node
->isElementNode() && toElement(node
)->isTextFormControl())) {
719 node
= ComposedTreeTraversal::nextSkippingChildren(*node
, this);
723 // Skip elements with valid dir attribute
724 if (node
->isElementNode()) {
725 AtomicString dirAttributeValue
= toElement(node
)->fastGetAttribute(dirAttr
);
726 if (isValidDirAttribute(dirAttributeValue
)) {
727 node
= ComposedTreeTraversal::nextSkippingChildren(*node
, this);
732 if (node
->isTextNode()) {
733 bool hasStrongDirectionality
;
734 TextDirection textDirection
= determineDirectionality(node
->textContent(true), &hasStrongDirectionality
);
735 if (hasStrongDirectionality
) {
736 if (strongDirectionalityTextNode
)
737 *strongDirectionalityTextNode
= node
;
738 return textDirection
;
741 node
= ComposedTreeTraversal::next(*node
, this);
743 if (strongDirectionalityTextNode
)
744 *strongDirectionalityTextNode
= 0;
748 bool HTMLElement::selfOrAncestorHasDirAutoAttribute() const
750 return layoutObject() && layoutObject()->style() && layoutObject()->style()->selfOrAncestorHasDirAutoAttribute();
753 void HTMLElement::dirAttributeChanged(const AtomicString
& value
)
755 // If an ancestor has dir=auto, and this node has the first character,
756 // changes to dir attribute may affect the ancestor.
757 updateDistribution();
758 Element
* parent
= ComposedTreeTraversal::parentElement(*this);
759 if (parent
&& parent
->isHTMLElement() && toHTMLElement(parent
)->selfOrAncestorHasDirAutoAttribute())
760 toHTMLElement(parent
)->adjustDirectionalityIfNeededAfterChildAttributeChanged(this);
762 if (equalIgnoringCase(value
, "auto"))
763 calculateAndAdjustDirectionality();
766 void HTMLElement::adjustDirectionalityIfNeededAfterChildAttributeChanged(Element
* child
)
768 ASSERT(selfOrAncestorHasDirAutoAttribute());
769 TextDirection textDirection
= directionality();
770 if (layoutObject() && layoutObject()->style() && layoutObject()->style()->direction() != textDirection
) {
771 Element
* elementToAdjust
= this;
772 for (; elementToAdjust
; elementToAdjust
= ComposedTreeTraversal::parentElement(*elementToAdjust
)) {
773 if (elementAffectsDirectionality(elementToAdjust
)) {
774 elementToAdjust
->setNeedsStyleRecalc(SubtreeStyleChange
, StyleChangeReasonForTracing::create(StyleChangeReason::WritingModeChange
));
781 void HTMLElement::calculateAndAdjustDirectionality()
783 TextDirection textDirection
= directionality();
784 if (layoutObject() && layoutObject()->style() && layoutObject()->style()->direction() != textDirection
)
785 setNeedsStyleRecalc(SubtreeStyleChange
, StyleChangeReasonForTracing::create(StyleChangeReason::WritingModeChange
));
788 void HTMLElement::adjustDirectionalityIfNeededAfterChildrenChanged(const ChildrenChange
& change
)
790 if (!selfOrAncestorHasDirAutoAttribute())
793 updateDistribution();
795 for (Element
* elementToAdjust
= this; elementToAdjust
; elementToAdjust
= ComposedTreeTraversal::parentElement(*elementToAdjust
)) {
796 if (elementAffectsDirectionality(elementToAdjust
)) {
797 toHTMLElement(elementToAdjust
)->calculateAndAdjustDirectionality();
803 void HTMLElement::addHTMLLengthToStyle(MutableStylePropertySet
* style
, CSSPropertyID propertyID
, const String
& value
)
805 // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
806 // length unit and make the appropriate parsed value.
808 // strip attribute garbage..
809 StringImpl
* v
= value
.impl();
813 while (length
< v
->length() && (*v
)[length
] <= ' ')
816 for (; length
< v
->length(); length
++) {
817 UChar cc
= (*v
)[length
];
821 if (cc
== '%' || cc
== '*') {
822 if (propertyID
== CSSPropertyWidth
)
823 UseCounter::count(document(), UseCounter::HTMLElementDeprecatedWidth
);
831 if (length
!= v
->length()) {
832 addPropertyToPresentationAttributeStyle(style
, propertyID
, v
->substring(0, length
));
837 addPropertyToPresentationAttributeStyle(style
, propertyID
, value
);
840 static RGBA32
parseColorStringWithCrazyLegacyRules(const String
& colorString
)
842 // Per spec, only look at the first 128 digits of the string.
843 const size_t maxColorLength
= 128;
844 // We'll pad the buffer with two extra 0s later, so reserve two more than the max.
845 Vector
<char, maxColorLength
+2> digitBuffer
;
849 if (colorString
[0] == '#')
852 // Grab the first 128 characters, replacing non-hex characters with 0.
853 // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String.
854 for (; i
< colorString
.length() && digitBuffer
.size() < maxColorLength
; i
++) {
855 if (!isASCIIHexDigit(colorString
[i
]))
856 digitBuffer
.append('0');
858 digitBuffer
.append(colorString
[i
]);
861 if (!digitBuffer
.size())
864 // Pad the buffer out to at least the next multiple of three in size.
865 digitBuffer
.append('0');
866 digitBuffer
.append('0');
868 if (digitBuffer
.size() < 6)
869 return makeRGB(toASCIIHexValue(digitBuffer
[0]), toASCIIHexValue(digitBuffer
[1]), toASCIIHexValue(digitBuffer
[2]));
871 // Split the digits into three components, then search the last 8 digits of each component.
872 ASSERT(digitBuffer
.size() >= 6);
873 size_t componentLength
= digitBuffer
.size() / 3;
874 size_t componentSearchWindowLength
= min
<size_t>(componentLength
, 8);
875 size_t redIndex
= componentLength
- componentSearchWindowLength
;
876 size_t greenIndex
= componentLength
* 2 - componentSearchWindowLength
;
877 size_t blueIndex
= componentLength
* 3 - componentSearchWindowLength
;
878 // Skip digits until one of them is non-zero, or we've only got two digits left in the component.
879 while (digitBuffer
[redIndex
] == '0' && digitBuffer
[greenIndex
] == '0' && digitBuffer
[blueIndex
] == '0' && (componentLength
- redIndex
) > 2) {
884 ASSERT(redIndex
+ 1 < componentLength
);
885 ASSERT(greenIndex
>= componentLength
);
886 ASSERT(greenIndex
+ 1 < componentLength
* 2);
887 ASSERT(blueIndex
>= componentLength
* 2);
888 ASSERT_WITH_SECURITY_IMPLICATION(blueIndex
+ 1 < digitBuffer
.size());
890 int redValue
= toASCIIHexValue(digitBuffer
[redIndex
], digitBuffer
[redIndex
+ 1]);
891 int greenValue
= toASCIIHexValue(digitBuffer
[greenIndex
], digitBuffer
[greenIndex
+ 1]);
892 int blueValue
= toASCIIHexValue(digitBuffer
[blueIndex
], digitBuffer
[blueIndex
+ 1]);
893 return makeRGB(redValue
, greenValue
, blueValue
);
896 // Color parsing that matches HTML's "rules for parsing a legacy color value"
897 void HTMLElement::addHTMLColorToStyle(MutableStylePropertySet
* style
, CSSPropertyID propertyID
, const String
& attributeValue
)
899 // An empty string doesn't apply a color. (One containing only whitespace does, which is why this check occurs before stripping.)
900 if (attributeValue
.isEmpty())
903 String colorString
= attributeValue
.stripWhiteSpace();
905 // "transparent" doesn't apply a color either.
906 if (equalIgnoringCase(colorString
, "transparent"))
909 // If the string is a named CSS color or a 3/6-digit hex color, use that.
911 if (!parsedColor
.setFromString(colorString
))
912 parsedColor
.setRGB(parseColorStringWithCrazyLegacyRules(colorString
));
914 style
->setProperty(propertyID
, cssValuePool().createColorValue(parsedColor
.rgb()));
917 bool HTMLElement::isInteractiveContent() const
922 HTMLMenuElement
* HTMLElement::assignedContextMenu() const
924 if (HTMLMenuElement
* menu
= contextMenu())
927 return parentElement() && parentElement()->isHTMLElement() ? toHTMLElement(parentElement())->assignedContextMenu() : nullptr;
930 HTMLMenuElement
* HTMLElement::contextMenu() const
932 const AtomicString
& contextMenuId(fastGetAttribute(contextmenuAttr
));
933 if (contextMenuId
.isNull())
936 Element
* element
= treeScope().getElementById(contextMenuId
);
937 // Not checking if the menu element is of type "popup".
938 // Ignoring menu element type attribute is intentional according to the standard.
939 return isHTMLMenuElement(element
) ? toHTMLMenuElement(element
) : nullptr;
942 void HTMLElement::setContextMenu(HTMLMenuElement
* contextMenu
)
945 setAttribute(contextmenuAttr
, "");
949 // http://www.whatwg.org/specs/web-apps/current-work/multipage/infrastructure.html#reflecting-content-attributes-in-idl-attributes
950 // On setting, if the given element has an id attribute, and has the same home
951 // subtree as the element of the attribute being set, and the given element is the
952 // first element in that home subtree whose ID is the value of that id attribute,
953 // then the content attribute must be set to the value of that id attribute.
954 // Otherwise, the content attribute must be set to the empty string.
955 const AtomicString
& contextMenuId(contextMenu
->fastGetAttribute(idAttr
));
957 if (!contextMenuId
.isNull() && contextMenu
== treeScope().getElementById(contextMenuId
))
958 setAttribute(contextmenuAttr
, contextMenuId
);
960 setAttribute(contextmenuAttr
, "");
963 void HTMLElement::defaultEventHandler(Event
* event
)
965 if (event
->type() == EventTypeNames::keypress
&& event
->isKeyboardEvent()) {
966 handleKeypressEvent(toKeyboardEvent(event
));
967 if (event
->defaultHandled())
971 Element::defaultEventHandler(event
);
974 bool HTMLElement::matchesReadOnlyPseudoClass() const
976 return !matchesReadWritePseudoClass();
979 bool HTMLElement::matchesReadWritePseudoClass() const
981 if (fastHasAttribute(contenteditableAttr
)) {
982 const AtomicString
& value
= fastGetAttribute(contenteditableAttr
);
984 if (value
.isEmpty() || equalIgnoringCase(value
, "true") || equalIgnoringCase(value
, "plaintext-only"))
986 if (equalIgnoringCase(value
, "false"))
988 // All other values should be treated as "inherit".
991 return parentElement() && parentElement()->hasEditableStyle();
994 void HTMLElement::handleKeypressEvent(KeyboardEvent
* event
)
996 if (!isSpatialNavigationEnabled(document().frame()) || !supportsFocus())
998 // if the element is a text form control (like <input type=text> or <textarea>)
999 // or has contentEditable attribute on, we should enter a space or newline
1000 // even in spatial navigation mode instead of handling it as a "click" action.
1001 if (isTextFormControl() || isContentEditable())
1003 int charCode
= event
->charCode();
1004 if (charCode
== '\r' || charCode
== ' ') {
1005 dispatchSimulatedClick(event
);
1006 event
->setDefaultHandled();
1010 const AtomicString
& HTMLElement::eventParameterName()
1012 DEFINE_STATIC_LOCAL(const AtomicString
, eventString
, ("event", AtomicString::ConstructFromLiteral
));
1016 } // namespace blink
1020 // For use in the debugger
1021 void dumpInnerHTML(blink::HTMLElement
*);
1023 void dumpInnerHTML(blink::HTMLElement
* element
)
1025 printf("%s\n", element
->innerHTML().ascii().data());