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/ElementShadow.h"
43 #include "core/dom/shadow/ShadowRoot.h"
44 #include "core/editing/markup.h"
45 #include "core/events/EventListener.h"
46 #include "core/events/KeyboardEvent.h"
47 #include "core/frame/Settings.h"
48 #include "core/frame/UseCounter.h"
49 #include "core/html/HTMLBRElement.h"
50 #include "core/html/HTMLFormElement.h"
51 #include "core/html/HTMLInputElement.h"
52 #include "core/html/HTMLMenuElement.h"
53 #include "core/html/HTMLTemplateElement.h"
54 #include "core/html/HTMLTextFormControlElement.h"
55 #include "core/html/parser/HTMLParserIdioms.h"
56 #include "core/rendering/RenderObject.h"
57 #include "platform/Language.h"
58 #include "platform/text/BidiResolver.h"
59 #include "platform/text/BidiTextRun.h"
60 #include "platform/text/TextRunIterator.h"
61 #include "wtf/StdLibExtras.h"
62 #include "wtf/text/CString.h"
66 using namespace HTMLNames;
72 DEFINE_ELEMENT_FACTORY_WITH_TAGNAME(HTMLElement);
74 String HTMLElement::nodeName() const
76 // FIXME: Would be nice to have an atomicstring lookup based off uppercase
77 // chars that does not have to copy the string on a hit in the hash.
78 // FIXME: We should have a way to detect XHTML elements and replace the hasPrefix() check with it.
79 if (document().isHTMLDocument()) {
80 if (!tagQName().hasPrefix())
81 return tagQName().localNameUpper();
82 return Element::nodeName().upper();
84 return Element::nodeName();
87 bool HTMLElement::ieForbidsInsertHTML() const
89 // FIXME: Supposedly IE disallows settting innerHTML, outerHTML
90 // and createContextualFragment on these tags. We have no tests to
91 // verify this however, so this list could be totally wrong.
92 // This list was moved from the previous endTagRequirement() implementation.
93 // This is also called from editing and assumed to be the list of tags
94 // for which no end tag should be serialized. It's unclear if the list for
95 // IE compat and the list for serialization sanity are the same.
96 if (hasTagName(areaTag)
97 || hasTagName(baseTag)
98 || hasTagName(basefontTag)
100 || hasTagName(colTag)
101 || hasTagName(embedTag)
102 || hasTagName(frameTag)
104 || hasTagName(imageTag)
105 || hasTagName(imgTag)
106 || hasTagName(inputTag)
107 || hasTagName(linkTag)
108 || (RuntimeEnabledFeatures::contextMenuEnabled() && hasTagName(menuitemTag))
109 || hasTagName(metaTag)
110 || hasTagName(paramTag)
111 || hasTagName(sourceTag)
112 || hasTagName(wbrTag))
117 static inline CSSValueID unicodeBidiAttributeForDirAuto(HTMLElement* element)
119 if (element->hasTagName(preTag) || element->hasTagName(textareaTag))
120 return CSSValueWebkitPlaintext;
121 // FIXME: For bdo element, dir="auto" should result in "bidi-override isolate" but we don't support having multiple values in unicode-bidi yet.
122 // See https://bugs.webkit.org/show_bug.cgi?id=73164.
123 return CSSValueWebkitIsolate;
126 unsigned HTMLElement::parseBorderWidthAttribute(const AtomicString& value) const
128 unsigned borderWidth = 0;
129 if (value.isEmpty() || !parseHTMLNonNegativeInteger(value, borderWidth))
130 return hasTagName(tableTag) ? 1 : borderWidth;
134 void HTMLElement::applyBorderAttributeToStyle(const AtomicString& value, MutableStylePropertySet* style)
136 addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderWidth, parseBorderWidthAttribute(value), CSSPrimitiveValue::CSS_PX);
137 addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderStyle, CSSValueSolid);
140 void HTMLElement::mapLanguageAttributeToLocale(const AtomicString& value, MutableStylePropertySet* style)
142 if (!value.isEmpty()) {
143 // Have to quote so the locale id is treated as a string instead of as a CSS keyword.
144 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLocale, quoteCSSString(value));
146 // FIXME: Remove the following UseCounter code when we collect enough
148 UseCounter::count(document(), UseCounter::LangAttribute);
149 if (isHTMLHtmlElement(*this))
150 UseCounter::count(document(), UseCounter::LangAttributeOnHTML);
151 else if (isHTMLBodyElement(*this))
152 UseCounter::count(document(), UseCounter::LangAttributeOnBody);
153 String htmlLanguage = value.string();
154 size_t firstSeparator = htmlLanguage.find('-');
155 if (firstSeparator != kNotFound)
156 htmlLanguage = htmlLanguage.left(firstSeparator);
157 String uiLanguage = defaultLanguage();
158 firstSeparator = uiLanguage.find('-');
159 if (firstSeparator != kNotFound)
160 uiLanguage = uiLanguage.left(firstSeparator);
161 firstSeparator = uiLanguage.find('_');
162 if (firstSeparator != kNotFound)
163 uiLanguage = uiLanguage.left(firstSeparator);
164 if (!equalIgnoringCase(htmlLanguage, uiLanguage))
165 UseCounter::count(document(), UseCounter::LangAttributeDoesNotMatchToUILocale);
167 // The empty string means the language is explicitly unknown.
168 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLocale, CSSValueAuto);
172 bool HTMLElement::isPresentationAttribute(const QualifiedName& name) const
174 if (name == alignAttr || name == contenteditableAttr || name == hiddenAttr || name == langAttr || name.matches(XMLNames::langAttr) || name == draggableAttr || name == dirAttr)
176 return Element::isPresentationAttribute(name);
179 static inline bool isValidDirAttribute(const AtomicString& value)
181 return equalIgnoringCase(value, "auto") || equalIgnoringCase(value, "ltr") || equalIgnoringCase(value, "rtl");
184 void HTMLElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
186 if (name == alignAttr) {
187 if (equalIgnoringCase(value, "middle"))
188 addPropertyToPresentationAttributeStyle(style, CSSPropertyTextAlign, CSSValueCenter);
190 addPropertyToPresentationAttributeStyle(style, CSSPropertyTextAlign, value);
191 } else if (name == contenteditableAttr) {
192 if (value.isEmpty() || equalIgnoringCase(value, "true")) {
193 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserModify, CSSValueReadWrite);
194 addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord);
195 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLineBreak, CSSValueAfterWhiteSpace);
196 } else if (equalIgnoringCase(value, "plaintext-only")) {
197 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserModify, CSSValueReadWritePlaintextOnly);
198 addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord);
199 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLineBreak, CSSValueAfterWhiteSpace);
200 } else if (equalIgnoringCase(value, "false"))
201 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserModify, CSSValueReadOnly);
202 } else if (name == hiddenAttr) {
203 addPropertyToPresentationAttributeStyle(style, CSSPropertyDisplay, CSSValueNone);
204 } else if (name == draggableAttr) {
205 if (equalIgnoringCase(value, "true")) {
206 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserDrag, CSSValueElement);
207 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserSelect, CSSValueNone);
208 } else if (equalIgnoringCase(value, "false"))
209 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserDrag, CSSValueNone);
210 } else if (name == dirAttr) {
211 if (equalIgnoringCase(value, "auto"))
212 addPropertyToPresentationAttributeStyle(style, CSSPropertyUnicodeBidi, unicodeBidiAttributeForDirAuto(this));
214 if (isValidDirAttribute(value))
215 addPropertyToPresentationAttributeStyle(style, CSSPropertyDirection, value);
217 addPropertyToPresentationAttributeStyle(style, CSSPropertyDirection, "ltr");
218 if (!hasTagName(bdiTag) && !hasTagName(bdoTag) && !hasTagName(outputTag))
219 addPropertyToPresentationAttributeStyle(style, CSSPropertyUnicodeBidi, CSSValueEmbed);
221 } else if (name.matches(XMLNames::langAttr))
222 mapLanguageAttributeToLocale(value, style);
223 else if (name == langAttr) {
224 // xml:lang has a higher priority than lang.
225 if (!fastHasAttribute(XMLNames::langAttr))
226 mapLanguageAttributeToLocale(value, style);
228 Element::collectStyleForPresentationAttribute(name, value, style);
231 const AtomicString& HTMLElement::eventNameForAttributeName(const QualifiedName& attrName)
233 if (!attrName.namespaceURI().isNull())
236 if (!attrName.localName().startsWith("on", false))
239 typedef HashMap<AtomicString, AtomicString> StringToStringMap;
240 DEFINE_STATIC_LOCAL(StringToStringMap, attributeNameToEventNameMap, ());
241 if (!attributeNameToEventNameMap.size()) {
242 struct AttrToEventName {
243 const QualifiedName& attr;
244 const AtomicString& event;
246 AttrToEventName attrToEventNames[] = {
247 { onabortAttr, EventTypeNames::abort },
248 { onanimationendAttr, EventTypeNames::animationend },
249 { onanimationiterationAttr, EventTypeNames::animationiteration },
250 { onanimationstartAttr, EventTypeNames::animationstart },
251 { onautocompleteAttr, EventTypeNames::autocomplete },
252 { onautocompleteerrorAttr, EventTypeNames::autocompleteerror },
253 { onbeforecopyAttr, EventTypeNames::beforecopy },
254 { onbeforecutAttr, EventTypeNames::beforecut },
255 { onbeforepasteAttr, EventTypeNames::beforepaste },
256 { onblurAttr, EventTypeNames::blur },
257 { oncancelAttr, EventTypeNames::cancel },
258 { oncanplayAttr, EventTypeNames::canplay },
259 { oncanplaythroughAttr, EventTypeNames::canplaythrough },
260 { onchangeAttr, EventTypeNames::change },
261 { onclickAttr, EventTypeNames::click },
262 { oncloseAttr, EventTypeNames::close },
263 { oncontextmenuAttr, EventTypeNames::contextmenu },
264 { oncopyAttr, EventTypeNames::copy },
265 { oncuechangeAttr, EventTypeNames::cuechange },
266 { oncutAttr, EventTypeNames::cut },
267 { ondblclickAttr, EventTypeNames::dblclick },
268 { ondragAttr, EventTypeNames::drag },
269 { ondragendAttr, EventTypeNames::dragend },
270 { ondragenterAttr, EventTypeNames::dragenter },
271 { ondragleaveAttr, EventTypeNames::dragleave },
272 { ondragoverAttr, EventTypeNames::dragover },
273 { ondragstartAttr, EventTypeNames::dragstart },
274 { ondropAttr, EventTypeNames::drop },
275 { ondurationchangeAttr, EventTypeNames::durationchange },
276 { onemptiedAttr, EventTypeNames::emptied },
277 { onendedAttr, EventTypeNames::ended },
278 { onerrorAttr, EventTypeNames::error },
279 { onfocusAttr, EventTypeNames::focus },
280 { onfocusinAttr, EventTypeNames::focusin },
281 { onfocusoutAttr, EventTypeNames::focusout },
282 { oninputAttr, EventTypeNames::input },
283 { oninvalidAttr, EventTypeNames::invalid },
284 { onkeydownAttr, EventTypeNames::keydown },
285 { onkeypressAttr, EventTypeNames::keypress },
286 { onkeyupAttr, EventTypeNames::keyup },
287 { onloadAttr, EventTypeNames::load },
288 { onloadeddataAttr, EventTypeNames::loadeddata },
289 { onloadedmetadataAttr, EventTypeNames::loadedmetadata },
290 { onloadstartAttr, EventTypeNames::loadstart },
291 { onmousedownAttr, EventTypeNames::mousedown },
292 { onmouseenterAttr, EventTypeNames::mouseenter },
293 { onmouseleaveAttr, EventTypeNames::mouseleave },
294 { onmousemoveAttr, EventTypeNames::mousemove },
295 { onmouseoutAttr, EventTypeNames::mouseout },
296 { onmouseoverAttr, EventTypeNames::mouseover },
297 { onmouseupAttr, EventTypeNames::mouseup },
298 { onmousewheelAttr, EventTypeNames::mousewheel },
299 { onpasteAttr, EventTypeNames::paste },
300 { onpauseAttr, EventTypeNames::pause },
301 { onplayAttr, EventTypeNames::play },
302 { onplayingAttr, EventTypeNames::playing },
303 { onprogressAttr, EventTypeNames::progress },
304 { onratechangeAttr, EventTypeNames::ratechange },
305 { onresetAttr, EventTypeNames::reset },
306 { onresizeAttr, EventTypeNames::resize },
307 { onscrollAttr, EventTypeNames::scroll },
308 { onseekedAttr, EventTypeNames::seeked },
309 { onseekingAttr, EventTypeNames::seeking },
310 { onselectAttr, EventTypeNames::select },
311 { onselectstartAttr, EventTypeNames::selectstart },
312 { onshowAttr, EventTypeNames::show },
313 { onstalledAttr, EventTypeNames::stalled },
314 { onsubmitAttr, EventTypeNames::submit },
315 { onsuspendAttr, EventTypeNames::suspend },
316 { ontimeupdateAttr, EventTypeNames::timeupdate },
317 { ontoggleAttr, EventTypeNames::toggle },
318 { ontouchcancelAttr, EventTypeNames::touchcancel },
319 { ontouchendAttr, EventTypeNames::touchend },
320 { ontouchmoveAttr, EventTypeNames::touchmove },
321 { ontouchstartAttr, EventTypeNames::touchstart },
322 { ontransitionendAttr, EventTypeNames::webkitTransitionEnd },
323 { onvolumechangeAttr, EventTypeNames::volumechange },
324 { onwaitingAttr, EventTypeNames::waiting },
325 { onwebkitanimationendAttr, EventTypeNames::webkitAnimationEnd },
326 { onwebkitanimationiterationAttr, EventTypeNames::webkitAnimationIteration },
327 { onwebkitanimationstartAttr, EventTypeNames::webkitAnimationStart },
328 { onwebkitfullscreenchangeAttr, EventTypeNames::webkitfullscreenchange },
329 { onwebkitfullscreenerrorAttr, EventTypeNames::webkitfullscreenerror },
330 { onwebkittransitionendAttr, EventTypeNames::webkitTransitionEnd },
331 { onwheelAttr, EventTypeNames::wheel },
334 for (size_t i = 0; i < WTF_ARRAY_LENGTH(attrToEventNames); i++)
335 attributeNameToEventNameMap.set(attrToEventNames[i].attr.localName(), attrToEventNames[i].event);
338 return attributeNameToEventNameMap.get(attrName.localName());
341 void HTMLElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
343 if (name == tabindexAttr)
344 return Element::parseAttribute(name, value);
346 if (name == dirAttr) {
347 dirAttributeChanged(value);
349 const AtomicString& eventName = eventNameForAttributeName(name);
350 if (!eventName.isNull())
351 setAttributeEventListener(eventName, createAttributeEventListener(this, name, value, eventParameterName()));
355 PassRefPtrWillBeRawPtr<DocumentFragment> HTMLElement::textToFragment(const String& text, ExceptionState& exceptionState)
357 RefPtrWillBeRawPtr<DocumentFragment> fragment = DocumentFragment::create(document());
358 unsigned i, length = text.length();
360 for (unsigned start = 0; start < length; ) {
362 // Find next line break.
363 for (i = start; i < length; i++) {
365 if (c == '\r' || c == '\n')
369 fragment->appendChild(Text::create(document(), text.substring(start, i - start)), exceptionState);
370 if (exceptionState.hadException())
373 if (c == '\r' || c == '\n') {
374 fragment->appendChild(HTMLBRElement::create(document()), exceptionState);
375 if (exceptionState.hadException())
377 // Make sure \r\n doesn't result in two line breaks.
378 if (c == '\r' && i + 1 < length && text[i + 1] == '\n')
382 start = i + 1; // Character after line break.
388 static inline bool shouldProhibitSetInnerOuterText(const HTMLElement& element)
390 return element.hasTagName(colTag)
391 || element.hasTagName(colgroupTag)
392 || element.hasTagName(framesetTag)
393 || element.hasTagName(headTag)
394 || element.hasTagName(htmlTag)
395 || element.hasTagName(tableTag)
396 || element.hasTagName(tbodyTag)
397 || element.hasTagName(tfootTag)
398 || element.hasTagName(theadTag)
399 || element.hasTagName(trTag);
402 void HTMLElement::setInnerText(const String& text, ExceptionState& exceptionState)
404 if (ieForbidsInsertHTML()) {
405 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion.");
408 if (shouldProhibitSetInnerOuterText(*this)) {
409 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion.");
413 // FIXME: This doesn't take whitespace collapsing into account at all.
415 if (!text.contains('\n') && !text.contains('\r')) {
416 if (text.isEmpty()) {
420 replaceChildrenWithText(this, text, exceptionState);
424 // FIXME: Do we need to be able to detect preserveNewline style even when there's no renderer?
425 // FIXME: Can the renderer be out of date here? Do we need to call updateStyleIfNeeded?
426 // For example, for the contents of textarea elements that are display:none?
427 RenderObject* r = renderer();
428 if (r && r->style()->preserveNewline()) {
429 if (!text.contains('\r')) {
430 replaceChildrenWithText(this, text, exceptionState);
433 String textWithConsistentLineBreaks = text;
434 textWithConsistentLineBreaks.replace("\r\n", "\n");
435 textWithConsistentLineBreaks.replace('\r', '\n');
436 replaceChildrenWithText(this, textWithConsistentLineBreaks, exceptionState);
440 // Add text nodes and <br> elements.
441 RefPtrWillBeRawPtr<DocumentFragment> fragment = textToFragment(text, exceptionState);
442 if (!exceptionState.hadException())
443 replaceChildrenWithFragment(this, fragment.release(), exceptionState);
446 void HTMLElement::setOuterText(const String& text, ExceptionState& exceptionState)
448 if (ieForbidsInsertHTML()) {
449 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion.");
452 if (shouldProhibitSetInnerOuterText(*this)) {
453 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion.");
457 ContainerNode* parent = parentNode();
459 exceptionState.throwDOMException(NoModificationAllowedError, "The element has no parent.");
463 RefPtrWillBeRawPtr<Node> prev = previousSibling();
464 RefPtrWillBeRawPtr<Node> next = nextSibling();
465 RefPtrWillBeRawPtr<Node> newChild = nullptr;
467 // Convert text to fragment with <br> tags instead of linebreaks if needed.
468 if (text.contains('\r') || text.contains('\n'))
469 newChild = textToFragment(text, exceptionState);
471 newChild = Text::create(document(), text);
473 // textToFragment might cause mutation events.
475 exceptionState.throwDOMException(HierarchyRequestError, "The element has no parent.");
477 if (exceptionState.hadException())
480 parent->replaceChild(newChild.release(), this, exceptionState);
482 RefPtrWillBeRawPtr<Node> node = next ? next->previousSibling() : nullptr;
483 if (!exceptionState.hadException() && node && node->isTextNode())
484 mergeWithNextTextNode(toText(node.get()), exceptionState);
486 if (!exceptionState.hadException() && prev && prev->isTextNode())
487 mergeWithNextTextNode(toText(prev.get()), exceptionState);
490 void HTMLElement::applyAlignmentAttributeToStyle(const AtomicString& alignment, MutableStylePropertySet* style)
492 // Vertical alignment with respect to the current baseline of the text
493 // right or left means floating images.
494 CSSValueID floatValue = CSSValueInvalid;
495 CSSValueID verticalAlignValue = CSSValueInvalid;
497 if (equalIgnoringCase(alignment, "absmiddle"))
498 verticalAlignValue = CSSValueMiddle;
499 else if (equalIgnoringCase(alignment, "absbottom"))
500 verticalAlignValue = CSSValueBottom;
501 else if (equalIgnoringCase(alignment, "left")) {
502 floatValue = CSSValueLeft;
503 verticalAlignValue = CSSValueTop;
504 } else if (equalIgnoringCase(alignment, "right")) {
505 floatValue = CSSValueRight;
506 verticalAlignValue = CSSValueTop;
507 } else if (equalIgnoringCase(alignment, "top"))
508 verticalAlignValue = CSSValueTop;
509 else if (equalIgnoringCase(alignment, "middle"))
510 verticalAlignValue = CSSValueWebkitBaselineMiddle;
511 else if (equalIgnoringCase(alignment, "center"))
512 verticalAlignValue = CSSValueMiddle;
513 else if (equalIgnoringCase(alignment, "bottom"))
514 verticalAlignValue = CSSValueBaseline;
515 else if (equalIgnoringCase(alignment, "texttop"))
516 verticalAlignValue = CSSValueTextTop;
518 if (floatValue != CSSValueInvalid)
519 addPropertyToPresentationAttributeStyle(style, CSSPropertyFloat, floatValue);
521 if (verticalAlignValue != CSSValueInvalid)
522 addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, verticalAlignValue);
525 bool HTMLElement::hasCustomFocusLogic() const
530 String HTMLElement::contentEditable() const
532 const AtomicString& value = fastGetAttribute(contenteditableAttr);
536 if (value.isEmpty() || equalIgnoringCase(value, "true"))
538 if (equalIgnoringCase(value, "false"))
540 if (equalIgnoringCase(value, "plaintext-only"))
541 return "plaintext-only";
546 void HTMLElement::setContentEditable(const String& enabled, ExceptionState& exceptionState)
548 if (equalIgnoringCase(enabled, "true"))
549 setAttribute(contenteditableAttr, "true");
550 else if (equalIgnoringCase(enabled, "false"))
551 setAttribute(contenteditableAttr, "false");
552 else if (equalIgnoringCase(enabled, "plaintext-only"))
553 setAttribute(contenteditableAttr, "plaintext-only");
554 else if (equalIgnoringCase(enabled, "inherit"))
555 removeAttribute(contenteditableAttr);
557 exceptionState.throwDOMException(SyntaxError, "The value provided ('" + enabled + "') is not one of 'true', 'false', 'plaintext-only', or 'inherit'.");
560 bool HTMLElement::draggable() const
562 return equalIgnoringCase(getAttribute(draggableAttr), "true");
565 void HTMLElement::setDraggable(bool value)
567 setAttribute(draggableAttr, value ? "true" : "false");
570 bool HTMLElement::spellcheck() const
572 return isSpellCheckingEnabled();
575 void HTMLElement::setSpellcheck(bool enable)
577 setAttribute(spellcheckAttr, enable ? "true" : "false");
581 void HTMLElement::click()
583 dispatchSimulatedClick(0, SendNoEvents);
586 void HTMLElement::accessKeyAction(bool sendMouseEvents)
588 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
591 String HTMLElement::title() const
593 return fastGetAttribute(titleAttr);
596 short HTMLElement::tabIndex() const
599 return Element::tabIndex();
603 TranslateAttributeMode HTMLElement::translateAttributeMode() const
605 const AtomicString& value = getAttribute(translateAttr);
607 if (value == nullAtom)
608 return TranslateAttributeInherit;
609 if (equalIgnoringCase(value, "yes") || equalIgnoringCase(value, ""))
610 return TranslateAttributeYes;
611 if (equalIgnoringCase(value, "no"))
612 return TranslateAttributeNo;
614 return TranslateAttributeInherit;
617 bool HTMLElement::translate() const
619 for (const HTMLElement* element = this; element; element = Traversal<HTMLElement>::firstAncestor(*element)) {
620 TranslateAttributeMode mode = element->translateAttributeMode();
621 if (mode != TranslateAttributeInherit) {
622 ASSERT(mode == TranslateAttributeYes || mode == TranslateAttributeNo);
623 return mode == TranslateAttributeYes;
627 // Default on the root element is translate=yes.
631 void HTMLElement::setTranslate(bool enable)
633 setAttribute(translateAttr, enable ? "yes" : "no");
636 // Returns the conforming 'dir' value associated with the state the attribute is in (in its canonical case), if any,
637 // or the empty string if the attribute is in a state that has no associated keyword value or if the attribute is
638 // not in a defined state (e.g. the attribute is missing and there is no missing value default).
639 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#limited-to-only-known-values
640 static inline const AtomicString& toValidDirValue(const AtomicString& value)
642 DEFINE_STATIC_LOCAL(const AtomicString, ltrValue, ("ltr", AtomicString::ConstructFromLiteral));
643 DEFINE_STATIC_LOCAL(const AtomicString, rtlValue, ("rtl", AtomicString::ConstructFromLiteral));
644 DEFINE_STATIC_LOCAL(const AtomicString, autoValue, ("auto", AtomicString::ConstructFromLiteral));
646 if (equalIgnoringCase(value, ltrValue))
648 if (equalIgnoringCase(value, rtlValue))
650 if (equalIgnoringCase(value, autoValue))
655 const AtomicString& HTMLElement::dir()
657 return toValidDirValue(fastGetAttribute(dirAttr));
660 void HTMLElement::setDir(const AtomicString& value)
662 setAttribute(dirAttr, value);
665 HTMLFormElement* HTMLElement::findFormAncestor() const
667 return Traversal<HTMLFormElement>::firstAncestor(*this);
670 static inline bool elementAffectsDirectionality(const Node* node)
672 return node->isHTMLElement() && (isHTMLBDIElement(toHTMLElement(*node)) || toHTMLElement(*node).hasAttribute(dirAttr));
675 static void setHasDirAutoFlagRecursively(Node* firstNode, bool flag, Node* lastNode = 0)
677 firstNode->setSelfOrAncestorHasDirAutoAttribute(flag);
679 Node* node = firstNode->firstChild();
682 if (elementAffectsDirectionality(node)) {
683 if (node == lastNode)
685 node = NodeTraversal::nextSkippingChildren(*node, firstNode);
688 node->setSelfOrAncestorHasDirAutoAttribute(flag);
689 if (node == lastNode)
691 node = NodeTraversal::next(*node, firstNode);
695 void HTMLElement::childrenChanged(const ChildrenChange& change)
697 Element::childrenChanged(change);
698 adjustDirectionalityIfNeededAfterChildrenChanged(change);
701 bool HTMLElement::hasDirectionAuto() const
703 const AtomicString& direction = fastGetAttribute(dirAttr);
704 return (isHTMLBDIElement(*this) && direction == nullAtom) || equalIgnoringCase(direction, "auto");
707 TextDirection HTMLElement::directionalityIfhasDirAutoAttribute(bool& isAuto) const
709 if (!(selfOrAncestorHasDirAutoAttribute() && hasDirectionAuto())) {
715 return directionality();
718 TextDirection HTMLElement::directionality(Node** strongDirectionalityTextNode) const
720 if (isHTMLInputElement(*this)) {
721 HTMLInputElement* inputElement = toHTMLInputElement(const_cast<HTMLElement*>(this));
722 bool hasStrongDirectionality;
723 TextDirection textDirection = determineDirectionality(inputElement->value(), hasStrongDirectionality);
724 if (strongDirectionalityTextNode)
725 *strongDirectionalityTextNode = hasStrongDirectionality ? inputElement : 0;
726 return textDirection;
729 Node* node = firstChild();
731 // Skip bdi, script, style and text form controls.
732 if (equalIgnoringCase(node->nodeName(), "bdi") || isHTMLScriptElement(*node) || isHTMLStyleElement(*node)
733 || (node->isElementNode() && toElement(node)->isTextFormControl())) {
734 node = NodeTraversal::nextSkippingChildren(*node, this);
738 // Skip elements with valid dir attribute
739 if (node->isElementNode()) {
740 AtomicString dirAttributeValue = toElement(node)->fastGetAttribute(dirAttr);
741 if (isValidDirAttribute(dirAttributeValue)) {
742 node = NodeTraversal::nextSkippingChildren(*node, this);
747 if (node->isTextNode()) {
748 bool hasStrongDirectionality;
749 TextDirection textDirection = determineDirectionality(node->textContent(true), hasStrongDirectionality);
750 if (hasStrongDirectionality) {
751 if (strongDirectionalityTextNode)
752 *strongDirectionalityTextNode = node;
753 return textDirection;
756 node = NodeTraversal::next(*node, this);
758 if (strongDirectionalityTextNode)
759 *strongDirectionalityTextNode = 0;
763 void HTMLElement::dirAttributeChanged(const AtomicString& value)
765 Element* parent = parentElement();
767 if (parent && parent->isHTMLElement() && parent->selfOrAncestorHasDirAutoAttribute())
768 toHTMLElement(parent)->adjustDirectionalityIfNeededAfterChildAttributeChanged(this);
770 if (equalIgnoringCase(value, "auto"))
771 calculateAndAdjustDirectionality();
774 void HTMLElement::adjustDirectionalityIfNeededAfterChildAttributeChanged(Element* child)
776 ASSERT(selfOrAncestorHasDirAutoAttribute());
777 Node* strongDirectionalityTextNode;
778 TextDirection textDirection = directionality(&strongDirectionalityTextNode);
779 setHasDirAutoFlagRecursively(child, false);
780 if (renderer() && renderer()->style() && renderer()->style()->direction() != textDirection) {
781 Element* elementToAdjust = this;
782 for (; elementToAdjust; elementToAdjust = elementToAdjust->parentElement()) {
783 if (elementAffectsDirectionality(elementToAdjust)) {
784 elementToAdjust->setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::WritingModeChange));
791 void HTMLElement::calculateAndAdjustDirectionality()
793 Node* strongDirectionalityTextNode;
794 TextDirection textDirection = directionality(&strongDirectionalityTextNode);
795 setHasDirAutoFlagRecursively(this, hasDirectionAuto(), strongDirectionalityTextNode);
796 for (ShadowRoot* root = youngestShadowRoot(); root; root = root->olderShadowRoot())
797 setHasDirAutoFlagRecursively(root, hasDirectionAuto());
798 if (renderer() && renderer()->style() && renderer()->style()->direction() != textDirection)
799 setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::WritingModeChange));
802 void HTMLElement::adjustDirectionalityIfNeededAfterChildrenChanged(const ChildrenChange& change)
804 if (!selfOrAncestorHasDirAutoAttribute())
807 Node* oldMarkedNode = change.siblingBeforeChange ? NodeTraversal::nextSkippingChildren(*change.siblingBeforeChange) : 0;
808 while (oldMarkedNode && elementAffectsDirectionality(oldMarkedNode))
809 oldMarkedNode = NodeTraversal::nextSkippingChildren(*oldMarkedNode, this);
811 setHasDirAutoFlagRecursively(oldMarkedNode, false);
813 for (Element* elementToAdjust = this; elementToAdjust; elementToAdjust = elementToAdjust->parentElement()) {
814 if (elementAffectsDirectionality(elementToAdjust)) {
815 toHTMLElement(elementToAdjust)->calculateAndAdjustDirectionality();
821 void HTMLElement::addHTMLLengthToStyle(MutableStylePropertySet* style, CSSPropertyID propertyID, const String& value)
823 // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
824 // length unit and make the appropriate parsed value.
826 // strip attribute garbage..
827 StringImpl* v = value.impl();
831 while (length < v->length() && (*v)[length] <= ' ')
834 for (; length < v->length(); length++) {
835 UChar cc = (*v)[length];
839 if (cc == '%' || cc == '*')
846 if (length != v->length()) {
847 addPropertyToPresentationAttributeStyle(style, propertyID, v->substring(0, length));
852 addPropertyToPresentationAttributeStyle(style, propertyID, value);
855 static RGBA32 parseColorStringWithCrazyLegacyRules(const String& colorString)
857 // Per spec, only look at the first 128 digits of the string.
858 const size_t maxColorLength = 128;
859 // We'll pad the buffer with two extra 0s later, so reserve two more than the max.
860 Vector<char, maxColorLength+2> digitBuffer;
864 if (colorString[0] == '#')
867 // Grab the first 128 characters, replacing non-hex characters with 0.
868 // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String.
869 for (; i < colorString.length() && digitBuffer.size() < maxColorLength; i++) {
870 if (!isASCIIHexDigit(colorString[i]))
871 digitBuffer.append('0');
873 digitBuffer.append(colorString[i]);
876 if (!digitBuffer.size())
879 // Pad the buffer out to at least the next multiple of three in size.
880 digitBuffer.append('0');
881 digitBuffer.append('0');
883 if (digitBuffer.size() < 6)
884 return makeRGB(toASCIIHexValue(digitBuffer[0]), toASCIIHexValue(digitBuffer[1]), toASCIIHexValue(digitBuffer[2]));
886 // Split the digits into three components, then search the last 8 digits of each component.
887 ASSERT(digitBuffer.size() >= 6);
888 size_t componentLength = digitBuffer.size() / 3;
889 size_t componentSearchWindowLength = min<size_t>(componentLength, 8);
890 size_t redIndex = componentLength - componentSearchWindowLength;
891 size_t greenIndex = componentLength * 2 - componentSearchWindowLength;
892 size_t blueIndex = componentLength * 3 - componentSearchWindowLength;
893 // Skip digits until one of them is non-zero, or we've only got two digits left in the component.
894 while (digitBuffer[redIndex] == '0' && digitBuffer[greenIndex] == '0' && digitBuffer[blueIndex] == '0' && (componentLength - redIndex) > 2) {
899 ASSERT(redIndex + 1 < componentLength);
900 ASSERT(greenIndex >= componentLength);
901 ASSERT(greenIndex + 1 < componentLength * 2);
902 ASSERT(blueIndex >= componentLength * 2);
903 ASSERT_WITH_SECURITY_IMPLICATION(blueIndex + 1 < digitBuffer.size());
905 int redValue = toASCIIHexValue(digitBuffer[redIndex], digitBuffer[redIndex + 1]);
906 int greenValue = toASCIIHexValue(digitBuffer[greenIndex], digitBuffer[greenIndex + 1]);
907 int blueValue = toASCIIHexValue(digitBuffer[blueIndex], digitBuffer[blueIndex + 1]);
908 return makeRGB(redValue, greenValue, blueValue);
911 // Color parsing that matches HTML's "rules for parsing a legacy color value"
912 void HTMLElement::addHTMLColorToStyle(MutableStylePropertySet* style, CSSPropertyID propertyID, const String& attributeValue)
914 // An empty string doesn't apply a color. (One containing only whitespace does, which is why this check occurs before stripping.)
915 if (attributeValue.isEmpty())
918 String colorString = attributeValue.stripWhiteSpace();
920 // "transparent" doesn't apply a color either.
921 if (equalIgnoringCase(colorString, "transparent"))
924 // If the string is a named CSS color or a 3/6-digit hex color, use that.
926 if (!parsedColor.setFromString(colorString))
927 parsedColor.setRGB(parseColorStringWithCrazyLegacyRules(colorString));
929 style->setProperty(propertyID, cssValuePool().createColorValue(parsedColor.rgb()));
932 bool HTMLElement::isInteractiveContent() const
938 HTMLMenuElement* HTMLElement::contextMenu() const
940 const AtomicString& contextMenuId(fastGetAttribute(contextmenuAttr));
941 if (contextMenuId.isNull())
944 Element* element = treeScope().getElementById(contextMenuId);
945 // Not checking if the menu element is of type "popup".
946 // Ignoring menu element type attribute is intentional according to the standard.
947 return isHTMLMenuElement(element) ? toHTMLMenuElement(element) : nullptr;
950 void HTMLElement::setContextMenu(HTMLMenuElement* contextMenu)
953 setAttribute(contextmenuAttr, "");
957 // http://www.whatwg.org/specs/web-apps/current-work/multipage/infrastructure.html#reflecting-content-attributes-in-idl-attributes
958 // On setting, if the given element has an id attribute, and has the same home
959 // subtree as the element of the attribute being set, and the given element is the
960 // first element in that home subtree whose ID is the value of that id attribute,
961 // then the content attribute must be set to the value of that id attribute.
962 // Otherwise, the content attribute must be set to the empty string.
963 const AtomicString& contextMenuId(contextMenu->fastGetAttribute(idAttr));
965 if (!contextMenuId.isNull() && contextMenu == treeScope().getElementById(contextMenuId))
966 setAttribute(contextmenuAttr, contextMenuId);
968 setAttribute(contextmenuAttr, "");
971 void HTMLElement::defaultEventHandler(Event* event)
973 if (event->type() == EventTypeNames::keypress && event->isKeyboardEvent()) {
974 handleKeypressEvent(toKeyboardEvent(event));
975 if (event->defaultHandled())
979 Element::defaultEventHandler(event);
982 bool HTMLElement::matchesReadOnlyPseudoClass() const
984 return !matchesReadWritePseudoClass();
987 bool HTMLElement::matchesReadWritePseudoClass() const
989 if (fastHasAttribute(contenteditableAttr)) {
990 const AtomicString& value = fastGetAttribute(contenteditableAttr);
992 if (value.isEmpty() || equalIgnoringCase(value, "true") || equalIgnoringCase(value, "plaintext-only"))
994 if (equalIgnoringCase(value, "false"))
996 // All other values should be treated as "inherit".
999 return parentElement() && parentElement()->hasEditableStyle();
1002 void HTMLElement::handleKeypressEvent(KeyboardEvent* event)
1004 if (!document().settings() || !document().settings()->spatialNavigationEnabled() || !supportsFocus())
1006 // if the element is a text form control (like <input type=text> or <textarea>)
1007 // or has contentEditable attribute on, we should enter a space or newline
1008 // even in spatial navigation mode instead of handling it as a "click" action.
1009 if (isTextFormControl() || isContentEditable())
1011 int charCode = event->charCode();
1012 if (charCode == '\r' || charCode == ' ') {
1013 dispatchSimulatedClick(event);
1014 event->setDefaultHandled();
1018 const AtomicString& HTMLElement::eventParameterName()
1020 DEFINE_STATIC_LOCAL(const AtomicString, eventString, ("event", AtomicString::ConstructFromLiteral));
1024 } // namespace blink
1028 // For use in the debugger
1029 void dumpInnerHTML(blink::HTMLElement*);
1031 void dumpInnerHTML(blink::HTMLElement* element)
1033 printf("%s\n", element->innerHTML().ascii().data());