Keep auxilliary media objects on the heap always.
[chromium-blink-merge.git] / third_party / WebKit / Source / core / html / HTMLElement.cpp
blobfb1a964845947b094cc3ef48cb2822e61b3815c2
1 /*
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.
25 #include "config.h"
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"
66 namespace blink {
68 using namespace HTMLNames;
69 using namespace WTF;
71 using namespace std;
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)
100 || hasTagName(brTag)
101 || hasTagName(colTag)
102 || hasTagName(embedTag)
103 || hasTagName(frameTag)
104 || hasTagName(hrTag)
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))
114 return true;
115 return false;
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())
132 return 1;
134 return borderWidth;
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
150 // data.
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);
169 } else {
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)
178 return true;
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);
192 else
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));
218 } else {
219 if (isValidDirAttribute(value))
220 addPropertyToPresentationAttributeStyle(style, CSSPropertyDirection, value);
221 else
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);
232 } else {
233 Element::collectStyleForPresentationAttribute(name, value, style);
237 const AtomicString& HTMLElement::eventNameForAttributeName(const QualifiedName& attrName)
239 if (!attrName.namespaceURI().isNull())
240 return nullAtom;
242 if (!attrName.localName().startsWith("on", TextCaseInsensitive))
243 return nullAtom;
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);
354 } else {
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();
365 UChar c = 0;
366 for (unsigned start = 0; start < length; ) {
368 // Find next line break.
369 for (i = start; i < length; i++) {
370 c = text[i];
371 if (c == '\r' || c == '\n')
372 break;
375 fragment->appendChild(Text::create(document(), text.substring(start, i - start)), exceptionState);
376 if (exceptionState.hadException())
377 return nullptr;
379 if (c == '\r' || c == '\n') {
380 fragment->appendChild(HTMLBRElement::create(document()), exceptionState);
381 if (exceptionState.hadException())
382 return nullptr;
383 // Make sure \r\n doesn't result in two line breaks.
384 if (c == '\r' && i + 1 < length && text[i + 1] == '\n')
385 i++;
388 start = i + 1; // Character after line break.
391 return fragment;
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.");
412 return;
414 if (shouldProhibitSetInnerOuterText(*this)) {
415 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion.");
416 return;
419 // FIXME: This doesn't take whitespace collapsing into account at all.
421 if (!text.contains('\n') && !text.contains('\r')) {
422 if (text.isEmpty()) {
423 removeChildren();
424 return;
426 replaceChildrenWithText(this, text, exceptionState);
427 return;
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);
437 return;
439 String textWithConsistentLineBreaks = text;
440 textWithConsistentLineBreaks.replace("\r\n", "\n");
441 textWithConsistentLineBreaks.replace('\r', '\n');
442 replaceChildrenWithText(this, textWithConsistentLineBreaks, exceptionState);
443 return;
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.");
456 return;
458 if (shouldProhibitSetInnerOuterText(*this)) {
459 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion.");
460 return;
463 ContainerNode* parent = parentNode();
464 if (!parent) {
465 exceptionState.throwDOMException(NoModificationAllowedError, "The element has no parent.");
466 return;
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);
476 else
477 newChild = Text::create(document(), text);
479 // textToFragment might cause mutation events.
480 if (!parentNode())
481 exceptionState.throwDOMException(HierarchyRequestError, "The element has no parent.");
483 if (exceptionState.hadException())
484 return;
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
534 return false;
537 String HTMLElement::contentEditable() const
539 const AtomicString& value = fastGetAttribute(contenteditableAttr);
541 if (value.isNull())
542 return "inherit";
543 if (value.isEmpty() || equalIgnoringCase(value, "true"))
544 return "true";
545 if (equalIgnoringCase(value, "false"))
546 return "false";
547 if (equalIgnoringCase(value, "plaintext-only"))
548 return "plaintext-only";
550 return "inherit";
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);
563 else
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
604 if (supportsFocus())
605 return Element::tabIndex();
606 return -1;
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.
634 return true;
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))
653 return ltrValue;
654 if (equalIgnoringCase(value, rtlValue))
655 return rtlValue;
656 if (equalIgnoringCase(value, autoValue))
657 return autoValue;
658 return nullAtom;
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();
698 if (!isAuto)
699 return LTR;
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);
715 while (node) {
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);
720 continue;
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);
728 continue;
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;
745 return LTR;
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));
775 return;
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())
791 return;
793 updateDistribution();
795 for (Element* elementToAdjust = this; elementToAdjust; elementToAdjust = ComposedTreeTraversal::parentElement(*elementToAdjust)) {
796 if (elementAffectsDirectionality(elementToAdjust)) {
797 toHTMLElement(elementToAdjust)->calculateAndAdjustDirectionality();
798 return;
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();
810 if (v) {
811 unsigned length = 0;
813 while (length < v->length() && (*v)[length] <= ' ')
814 length++;
816 for (; length < v->length(); length++) {
817 UChar cc = (*v)[length];
818 if (cc > '9')
819 break;
820 if (cc < '0') {
821 if (cc == '%' || cc == '*') {
822 if (propertyID == CSSPropertyWidth)
823 UseCounter::count(document(), UseCounter::HTMLElementDeprecatedWidth);
824 length++;
826 if (cc != '.')
827 break;
831 if (length != v->length()) {
832 addPropertyToPresentationAttributeStyle(style, propertyID, v->substring(0, length));
833 return;
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;
847 size_t i = 0;
848 // Skip a leading #.
849 if (colorString[0] == '#')
850 i = 1;
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');
857 else
858 digitBuffer.append(colorString[i]);
861 if (!digitBuffer.size())
862 return Color::black;
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) {
880 redIndex++;
881 greenIndex++;
882 blueIndex++;
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())
901 return;
903 String colorString = attributeValue.stripWhiteSpace();
905 // "transparent" doesn't apply a color either.
906 if (equalIgnoringCase(colorString, "transparent"))
907 return;
909 // If the string is a named CSS color or a 3/6-digit hex color, use that.
910 Color parsedColor;
911 if (!parsedColor.setFromString(colorString))
912 parsedColor.setRGB(parseColorStringWithCrazyLegacyRules(colorString));
914 style->setProperty(propertyID, cssValuePool().createColorValue(parsedColor.rgb()));
917 bool HTMLElement::isInteractiveContent() const
919 return false;
922 HTMLMenuElement* HTMLElement::assignedContextMenu() const
924 if (HTMLMenuElement* menu = contextMenu())
925 return menu;
927 return parentElement() && parentElement()->isHTMLElement() ? toHTMLElement(parentElement())->assignedContextMenu() : nullptr;
930 HTMLMenuElement* HTMLElement::contextMenu() const
932 const AtomicString& contextMenuId(fastGetAttribute(contextmenuAttr));
933 if (contextMenuId.isNull())
934 return nullptr;
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)
944 if (!contextMenu) {
945 setAttribute(contextmenuAttr, "");
946 return;
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);
959 else
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())
968 return;
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"))
985 return true;
986 if (equalIgnoringCase(value, "false"))
987 return 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())
997 return;
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())
1002 return;
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));
1013 return eventString;
1016 } // namespace blink
1018 #ifndef NDEBUG
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());
1027 #endif