1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) 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. 7 * 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. 12 * 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. 17 * 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. 22 * 23 */ 24 25 #include "config.h" 26 #include "core/html/HTMLElement.h" 27 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/html/HTMLBRElement.h" 49 #include "core/html/HTMLFormElement.h" 50 #include "core/html/HTMLInputElement.h" 51 #include "core/html/HTMLMenuElement.h" 52 #include "core/html/HTMLTemplateElement.h" 53 #include "core/html/HTMLTextFormControlElement.h" 54 #include "core/html/parser/HTMLParserIdioms.h" 55 #include "core/rendering/RenderObject.h" 56 #include "platform/text/BidiResolver.h" 57 #include "platform/text/BidiTextRun.h" 58 #include "platform/text/TextRunIterator.h" 59 #include "wtf/StdLibExtras.h" 60 #include "wtf/text/CString.h" 61 62 namespace blink { 63 64 using namespace HTMLNames; 65 using namespace WTF; 66 67 using std::min; 68 using std::max; 69 70 DEFINE_ELEMENT_FACTORY_WITH_TAGNAME(HTMLElement); 71 72 String HTMLElement::nodeName() const 73 { 74 // FIXME: Would be nice to have an atomicstring lookup based off uppercase 75 // chars that does not have to copy the string on a hit in the hash. 76 // FIXME: We should have a way to detect XHTML elements and replace the hasPrefix() check with it. 77 if (document().isHTMLDocument()) { 78 if (!tagQName().hasPrefix()) 79 return tagQName().localNameUpper(); 80 return Element::nodeName().upper(); 81 } 82 return Element::nodeName(); 83 } 84 85 bool HTMLElement::ieForbidsInsertHTML() const 86 { 87 // FIXME: Supposedly IE disallows settting innerHTML, outerHTML 88 // and createContextualFragment on these tags. We have no tests to 89 // verify this however, so this list could be totally wrong. 90 // This list was moved from the previous endTagRequirement() implementation. 91 // This is also called from editing and assumed to be the list of tags 92 // for which no end tag should be serialized. It's unclear if the list for 93 // IE compat and the list for serialization sanity are the same. 94 if (hasTagName(areaTag) 95 || hasTagName(baseTag) 96 || hasTagName(basefontTag) 97 || hasTagName(brTag) 98 || hasTagName(colTag) 99 || hasTagName(embedTag) 100 || hasTagName(frameTag) 101 || hasTagName(hrTag) 102 || hasTagName(imageTag) 103 || hasTagName(imgTag) 104 || hasTagName(inputTag) 105 || hasTagName(linkTag) 106 || (RuntimeEnabledFeatures::contextMenuEnabled() && hasTagName(menuitemTag)) 107 || hasTagName(metaTag) 108 || hasTagName(paramTag) 109 || hasTagName(sourceTag) 110 || hasTagName(wbrTag)) 111 return true; 112 return false; 113 } 114 115 static inline CSSValueID unicodeBidiAttributeForDirAuto(HTMLElement* element) 116 { 117 if (element->hasTagName(preTag) || element->hasTagName(textareaTag)) 118 return CSSValueWebkitPlaintext; 119 // FIXME: For bdo element, dir="auto" should result in "bidi-override isolate" but we don't support having multiple values in unicode-bidi yet. 120 // See https://bugs.webkit.org/show_bug.cgi?id=73164. 121 return CSSValueWebkitIsolate; 122 } 123 124 unsigned HTMLElement::parseBorderWidthAttribute(const AtomicString& value) const 125 { 126 unsigned borderWidth = 0; 127 if (value.isEmpty() || !parseHTMLNonNegativeInteger(value, borderWidth)) 128 return hasTagName(tableTag) ? 1 : borderWidth; 129 return borderWidth; 130 } 131 132 void HTMLElement::applyBorderAttributeToStyle(const AtomicString& value, MutableStylePropertySet* style) 133 { 134 addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderWidth, parseBorderWidthAttribute(value), CSSPrimitiveValue::CSS_PX); 135 addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderStyle, CSSValueSolid); 136 } 137 138 void HTMLElement::mapLanguageAttributeToLocale(const AtomicString& value, MutableStylePropertySet* style) 139 { 140 if (!value.isEmpty()) { 141 // Have to quote so the locale id is treated as a string instead of as a CSS keyword. 142 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLocale, quoteCSSString(value)); 143 } else { 144 // The empty string means the language is explicitly unknown. 145 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLocale, CSSValueAuto); 146 } 147 } 148 149 bool HTMLElement::isPresentationAttribute(const QualifiedName& name) const 150 { 151 if (name == alignAttr || name == contenteditableAttr || name == hiddenAttr || name == langAttr || name.matches(XMLNames::langAttr) || name == draggableAttr || name == dirAttr) 152 return true; 153 return Element::isPresentationAttribute(name); 154 } 155 156 static inline bool isValidDirAttribute(const AtomicString& value) 157 { 158 return equalIgnoringCase(value, "auto") || equalIgnoringCase(value, "ltr") || equalIgnoringCase(value, "rtl"); 159 } 160 161 void HTMLElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style) 162 { 163 if (name == alignAttr) { 164 if (equalIgnoringCase(value, "middle")) 165 addPropertyToPresentationAttributeStyle(style, CSSPropertyTextAlign, CSSValueCenter); 166 else 167 addPropertyToPresentationAttributeStyle(style, CSSPropertyTextAlign, value); 168 } else if (name == contenteditableAttr) { 169 if (value.isEmpty() || equalIgnoringCase(value, "true")) { 170 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserModify, CSSValueReadWrite); 171 addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord); 172 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLineBreak, CSSValueAfterWhiteSpace); 173 } else if (equalIgnoringCase(value, "plaintext-only")) { 174 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserModify, CSSValueReadWritePlaintextOnly); 175 addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord); 176 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLineBreak, CSSValueAfterWhiteSpace); 177 } else if (equalIgnoringCase(value, "false")) 178 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserModify, CSSValueReadOnly); 179 } else if (name == hiddenAttr) { 180 addPropertyToPresentationAttributeStyle(style, CSSPropertyDisplay, CSSValueNone); 181 } else if (name == draggableAttr) { 182 if (equalIgnoringCase(value, "true")) { 183 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserDrag, CSSValueElement); 184 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserSelect, CSSValueNone); 185 } else if (equalIgnoringCase(value, "false")) 186 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserDrag, CSSValueNone); 187 } else if (name == dirAttr) { 188 if (equalIgnoringCase(value, "auto")) 189 addPropertyToPresentationAttributeStyle(style, CSSPropertyUnicodeBidi, unicodeBidiAttributeForDirAuto(this)); 190 else { 191 if (isValidDirAttribute(value)) 192 addPropertyToPresentationAttributeStyle(style, CSSPropertyDirection, value); 193 else 194 addPropertyToPresentationAttributeStyle(style, CSSPropertyDirection, "ltr"); 195 if (!hasTagName(bdiTag) && !hasTagName(bdoTag) && !hasTagName(outputTag)) 196 addPropertyToPresentationAttributeStyle(style, CSSPropertyUnicodeBidi, CSSValueEmbed); 197 } 198 } else if (name.matches(XMLNames::langAttr)) 199 mapLanguageAttributeToLocale(value, style); 200 else if (name == langAttr) { 201 // xml:lang has a higher priority than lang. 202 if (!fastHasAttribute(XMLNames::langAttr)) 203 mapLanguageAttributeToLocale(value, style); 204 } else 205 Element::collectStyleForPresentationAttribute(name, value, style); 206 } 207 208 const AtomicString& HTMLElement::eventNameForAttributeName(const QualifiedName& attrName) 209 { 210 if (!attrName.namespaceURI().isNull()) 211 return nullAtom; 212 213 if (!attrName.localName().startsWith("on", false)) 214 return nullAtom; 215 216 typedef HashMap<AtomicString, AtomicString> StringToStringMap; 217 DEFINE_STATIC_LOCAL(StringToStringMap, attributeNameToEventNameMap, ()); 218 if (!attributeNameToEventNameMap.size()) { 219 attributeNameToEventNameMap.set(onabortAttr.localName(), EventTypeNames::abort); 220 attributeNameToEventNameMap.set(onanimationendAttr.localName(), EventTypeNames::animationend); 221 attributeNameToEventNameMap.set(onanimationiterationAttr.localName(), EventTypeNames::animationiteration); 222 attributeNameToEventNameMap.set(onanimationstartAttr.localName(), EventTypeNames::animationstart); 223 attributeNameToEventNameMap.set(onautocompleteAttr.localName(), EventTypeNames::autocomplete); 224 attributeNameToEventNameMap.set(onautocompleteerrorAttr.localName(), EventTypeNames::autocompleteerror); 225 attributeNameToEventNameMap.set(onbeforecopyAttr.localName(), EventTypeNames::beforecopy); 226 attributeNameToEventNameMap.set(onbeforecutAttr.localName(), EventTypeNames::beforecut); 227 attributeNameToEventNameMap.set(onbeforepasteAttr.localName(), EventTypeNames::beforepaste); 228 attributeNameToEventNameMap.set(onblurAttr.localName(), EventTypeNames::blur); 229 attributeNameToEventNameMap.set(oncancelAttr.localName(), EventTypeNames::cancel); 230 attributeNameToEventNameMap.set(oncanplayAttr.localName(), EventTypeNames::canplay); 231 attributeNameToEventNameMap.set(oncanplaythroughAttr.localName(), EventTypeNames::canplaythrough); 232 attributeNameToEventNameMap.set(onchangeAttr.localName(), EventTypeNames::change); 233 attributeNameToEventNameMap.set(onclickAttr.localName(), EventTypeNames::click); 234 attributeNameToEventNameMap.set(oncloseAttr.localName(), EventTypeNames::close); 235 attributeNameToEventNameMap.set(oncontextmenuAttr.localName(), EventTypeNames::contextmenu); 236 attributeNameToEventNameMap.set(oncopyAttr.localName(), EventTypeNames::copy); 237 attributeNameToEventNameMap.set(oncuechangeAttr.localName(), EventTypeNames::cuechange); 238 attributeNameToEventNameMap.set(oncutAttr.localName(), EventTypeNames::cut); 239 attributeNameToEventNameMap.set(ondblclickAttr.localName(), EventTypeNames::dblclick); 240 attributeNameToEventNameMap.set(ondragAttr.localName(), EventTypeNames::drag); 241 attributeNameToEventNameMap.set(ondragendAttr.localName(), EventTypeNames::dragend); 242 attributeNameToEventNameMap.set(ondragenterAttr.localName(), EventTypeNames::dragenter); 243 attributeNameToEventNameMap.set(ondragleaveAttr.localName(), EventTypeNames::dragleave); 244 attributeNameToEventNameMap.set(ondragoverAttr.localName(), EventTypeNames::dragover); 245 attributeNameToEventNameMap.set(ondragstartAttr.localName(), EventTypeNames::dragstart); 246 attributeNameToEventNameMap.set(ondropAttr.localName(), EventTypeNames::drop); 247 attributeNameToEventNameMap.set(ondurationchangeAttr.localName(), EventTypeNames::durationchange); 248 attributeNameToEventNameMap.set(onemptiedAttr.localName(), EventTypeNames::emptied); 249 attributeNameToEventNameMap.set(onendedAttr.localName(), EventTypeNames::ended); 250 attributeNameToEventNameMap.set(onerrorAttr.localName(), EventTypeNames::error); 251 attributeNameToEventNameMap.set(onfocusAttr.localName(), EventTypeNames::focus); 252 attributeNameToEventNameMap.set(onfocusinAttr.localName(), EventTypeNames::focusin); 253 attributeNameToEventNameMap.set(onfocusoutAttr.localName(), EventTypeNames::focusout); 254 attributeNameToEventNameMap.set(oninputAttr.localName(), EventTypeNames::input); 255 attributeNameToEventNameMap.set(oninvalidAttr.localName(), EventTypeNames::invalid); 256 attributeNameToEventNameMap.set(onkeydownAttr.localName(), EventTypeNames::keydown); 257 attributeNameToEventNameMap.set(onkeypressAttr.localName(), EventTypeNames::keypress); 258 attributeNameToEventNameMap.set(onkeyupAttr.localName(), EventTypeNames::keyup); 259 attributeNameToEventNameMap.set(onloadAttr.localName(), EventTypeNames::load); 260 attributeNameToEventNameMap.set(onloadeddataAttr.localName(), EventTypeNames::loadeddata); 261 attributeNameToEventNameMap.set(onloadedmetadataAttr.localName(), EventTypeNames::loadedmetadata); 262 attributeNameToEventNameMap.set(onloadstartAttr.localName(), EventTypeNames::loadstart); 263 attributeNameToEventNameMap.set(onmousedownAttr.localName(), EventTypeNames::mousedown); 264 attributeNameToEventNameMap.set(onmouseenterAttr.localName(), EventTypeNames::mouseenter); 265 attributeNameToEventNameMap.set(onmouseleaveAttr.localName(), EventTypeNames::mouseleave); 266 attributeNameToEventNameMap.set(onmousemoveAttr.localName(), EventTypeNames::mousemove); 267 attributeNameToEventNameMap.set(onmouseoutAttr.localName(), EventTypeNames::mouseout); 268 attributeNameToEventNameMap.set(onmouseoverAttr.localName(), EventTypeNames::mouseover); 269 attributeNameToEventNameMap.set(onmouseupAttr.localName(), EventTypeNames::mouseup); 270 attributeNameToEventNameMap.set(onmousewheelAttr.localName(), EventTypeNames::mousewheel); 271 attributeNameToEventNameMap.set(onpasteAttr.localName(), EventTypeNames::paste); 272 attributeNameToEventNameMap.set(onpauseAttr.localName(), EventTypeNames::pause); 273 attributeNameToEventNameMap.set(onplayAttr.localName(), EventTypeNames::play); 274 attributeNameToEventNameMap.set(onplayingAttr.localName(), EventTypeNames::playing); 275 attributeNameToEventNameMap.set(onprogressAttr.localName(), EventTypeNames::progress); 276 attributeNameToEventNameMap.set(onratechangeAttr.localName(), EventTypeNames::ratechange); 277 attributeNameToEventNameMap.set(onresetAttr.localName(), EventTypeNames::reset); 278 attributeNameToEventNameMap.set(onresizeAttr.localName(), EventTypeNames::resize); 279 attributeNameToEventNameMap.set(onscrollAttr.localName(), EventTypeNames::scroll); 280 attributeNameToEventNameMap.set(onseekedAttr.localName(), EventTypeNames::seeked); 281 attributeNameToEventNameMap.set(onseekingAttr.localName(), EventTypeNames::seeking); 282 attributeNameToEventNameMap.set(onselectAttr.localName(), EventTypeNames::select); 283 attributeNameToEventNameMap.set(onselectstartAttr.localName(), EventTypeNames::selectstart); 284 attributeNameToEventNameMap.set(onshowAttr.localName(), EventTypeNames::show); 285 attributeNameToEventNameMap.set(onstalledAttr.localName(), EventTypeNames::stalled); 286 attributeNameToEventNameMap.set(onsubmitAttr.localName(), EventTypeNames::submit); 287 attributeNameToEventNameMap.set(onsuspendAttr.localName(), EventTypeNames::suspend); 288 attributeNameToEventNameMap.set(ontimeupdateAttr.localName(), EventTypeNames::timeupdate); 289 attributeNameToEventNameMap.set(ontoggleAttr.localName(), EventTypeNames::toggle); 290 attributeNameToEventNameMap.set(ontouchcancelAttr.localName(), EventTypeNames::touchcancel); 291 attributeNameToEventNameMap.set(ontouchendAttr.localName(), EventTypeNames::touchend); 292 attributeNameToEventNameMap.set(ontouchmoveAttr.localName(), EventTypeNames::touchmove); 293 attributeNameToEventNameMap.set(ontouchstartAttr.localName(), EventTypeNames::touchstart); 294 attributeNameToEventNameMap.set(ontransitionendAttr.localName(), EventTypeNames::webkitTransitionEnd); 295 attributeNameToEventNameMap.set(onvolumechangeAttr.localName(), EventTypeNames::volumechange); 296 attributeNameToEventNameMap.set(onwaitingAttr.localName(), EventTypeNames::waiting); 297 attributeNameToEventNameMap.set(onwebkitanimationendAttr.localName(), EventTypeNames::webkitAnimationEnd); 298 attributeNameToEventNameMap.set(onwebkitanimationiterationAttr.localName(), EventTypeNames::webkitAnimationIteration); 299 attributeNameToEventNameMap.set(onwebkitanimationstartAttr.localName(), EventTypeNames::webkitAnimationStart); 300 attributeNameToEventNameMap.set(onwebkitfullscreenchangeAttr.localName(), EventTypeNames::webkitfullscreenchange); 301 attributeNameToEventNameMap.set(onwebkitfullscreenerrorAttr.localName(), EventTypeNames::webkitfullscreenerror); 302 attributeNameToEventNameMap.set(onwebkittransitionendAttr.localName(), EventTypeNames::webkitTransitionEnd); 303 attributeNameToEventNameMap.set(onwheelAttr.localName(), EventTypeNames::wheel); 304 } 305 306 return attributeNameToEventNameMap.get(attrName.localName()); 307 } 308 309 void HTMLElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 310 { 311 if (name == tabindexAttr) 312 return Element::parseAttribute(name, value); 313 314 if (name == dirAttr) { 315 dirAttributeChanged(value); 316 } else { 317 const AtomicString& eventName = eventNameForAttributeName(name); 318 if (!eventName.isNull()) 319 setAttributeEventListener(eventName, createAttributeEventListener(this, name, value, eventParameterName())); 320 } 321 } 322 323 PassRefPtrWillBeRawPtr<DocumentFragment> HTMLElement::textToFragment(const String& text, ExceptionState& exceptionState) 324 { 325 RefPtrWillBeRawPtr<DocumentFragment> fragment = DocumentFragment::create(document()); 326 unsigned i, length = text.length(); 327 UChar c = 0; 328 for (unsigned start = 0; start < length; ) { 329 330 // Find next line break. 331 for (i = start; i < length; i++) { 332 c = text[i]; 333 if (c == '\r' || c == '\n') 334 break; 335 } 336 337 fragment->appendChild(Text::create(document(), text.substring(start, i - start)), exceptionState); 338 if (exceptionState.hadException()) 339 return nullptr; 340 341 if (c == '\r' || c == '\n') { 342 fragment->appendChild(HTMLBRElement::create(document()), exceptionState); 343 if (exceptionState.hadException()) 344 return nullptr; 345 // Make sure \r\n doesn't result in two line breaks. 346 if (c == '\r' && i + 1 < length && text[i + 1] == '\n') 347 i++; 348 } 349 350 start = i + 1; // Character after line break. 351 } 352 353 return fragment; 354 } 355 356 static inline bool shouldProhibitSetInnerOuterText(const HTMLElement& element) 357 { 358 return element.hasTagName(colTag) 359 || element.hasTagName(colgroupTag) 360 || element.hasTagName(framesetTag) 361 || element.hasTagName(headTag) 362 || element.hasTagName(htmlTag) 363 || element.hasTagName(tableTag) 364 || element.hasTagName(tbodyTag) 365 || element.hasTagName(tfootTag) 366 || element.hasTagName(theadTag) 367 || element.hasTagName(trTag); 368 } 369 370 void HTMLElement::setInnerText(const String& text, ExceptionState& exceptionState) 371 { 372 if (ieForbidsInsertHTML()) { 373 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion."); 374 return; 375 } 376 if (shouldProhibitSetInnerOuterText(*this)) { 377 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion."); 378 return; 379 } 380 381 // FIXME: This doesn't take whitespace collapsing into account at all. 382 383 if (!text.contains('\n') && !text.contains('\r')) { 384 if (text.isEmpty()) { 385 removeChildren(); 386 return; 387 } 388 replaceChildrenWithText(this, text, exceptionState); 389 return; 390 } 391 392 // FIXME: Do we need to be able to detect preserveNewline style even when there's no renderer? 393 // FIXME: Can the renderer be out of date here? Do we need to call updateStyleIfNeeded? 394 // For example, for the contents of textarea elements that are display:none? 395 RenderObject* r = renderer(); 396 if (r && r->style()->preserveNewline()) { 397 if (!text.contains('\r')) { 398 replaceChildrenWithText(this, text, exceptionState); 399 return; 400 } 401 String textWithConsistentLineBreaks = text; 402 textWithConsistentLineBreaks.replace("\r\n", "\n"); 403 textWithConsistentLineBreaks.replace('\r', '\n'); 404 replaceChildrenWithText(this, textWithConsistentLineBreaks, exceptionState); 405 return; 406 } 407 408 // Add text nodes and <br> elements. 409 RefPtrWillBeRawPtr<DocumentFragment> fragment = textToFragment(text, exceptionState); 410 if (!exceptionState.hadException()) 411 replaceChildrenWithFragment(this, fragment.release(), exceptionState); 412 } 413 414 void HTMLElement::setOuterText(const String& text, ExceptionState& exceptionState) 415 { 416 if (ieForbidsInsertHTML()) { 417 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion."); 418 return; 419 } 420 if (shouldProhibitSetInnerOuterText(*this)) { 421 exceptionState.throwDOMException(NoModificationAllowedError, "The '" + localName() + "' element does not support text insertion."); 422 return; 423 } 424 425 ContainerNode* parent = parentNode(); 426 if (!parent) { 427 exceptionState.throwDOMException(NoModificationAllowedError, "The element has no parent."); 428 return; 429 } 430 431 RefPtrWillBeRawPtr<Node> prev = previousSibling(); 432 RefPtrWillBeRawPtr<Node> next = nextSibling(); 433 RefPtrWillBeRawPtr<Node> newChild = nullptr; 434 435 // Convert text to fragment with <br> tags instead of linebreaks if needed. 436 if (text.contains('\r') || text.contains('\n')) 437 newChild = textToFragment(text, exceptionState); 438 else 439 newChild = Text::create(document(), text); 440 441 // textToFragment might cause mutation events. 442 if (!parentNode()) 443 exceptionState.throwDOMException(HierarchyRequestError, "The element has no parent."); 444 445 if (exceptionState.hadException()) 446 return; 447 448 parent->replaceChild(newChild.release(), this, exceptionState); 449 450 RefPtrWillBeRawPtr<Node> node = next ? next->previousSibling() : nullptr; 451 if (!exceptionState.hadException() && node && node->isTextNode()) 452 mergeWithNextTextNode(toText(node.get()), exceptionState); 453 454 if (!exceptionState.hadException() && prev && prev->isTextNode()) 455 mergeWithNextTextNode(toText(prev.get()), exceptionState); 456 } 457 458 void HTMLElement::applyAlignmentAttributeToStyle(const AtomicString& alignment, MutableStylePropertySet* style) 459 { 460 // Vertical alignment with respect to the current baseline of the text 461 // right or left means floating images. 462 CSSValueID floatValue = CSSValueInvalid; 463 CSSValueID verticalAlignValue = CSSValueInvalid; 464 465 if (equalIgnoringCase(alignment, "absmiddle")) 466 verticalAlignValue = CSSValueMiddle; 467 else if (equalIgnoringCase(alignment, "absbottom")) 468 verticalAlignValue = CSSValueBottom; 469 else if (equalIgnoringCase(alignment, "left")) { 470 floatValue = CSSValueLeft; 471 verticalAlignValue = CSSValueTop; 472 } else if (equalIgnoringCase(alignment, "right")) { 473 floatValue = CSSValueRight; 474 verticalAlignValue = CSSValueTop; 475 } else if (equalIgnoringCase(alignment, "top")) 476 verticalAlignValue = CSSValueTop; 477 else if (equalIgnoringCase(alignment, "middle")) 478 verticalAlignValue = CSSValueWebkitBaselineMiddle; 479 else if (equalIgnoringCase(alignment, "center")) 480 verticalAlignValue = CSSValueMiddle; 481 else if (equalIgnoringCase(alignment, "bottom")) 482 verticalAlignValue = CSSValueBaseline; 483 else if (equalIgnoringCase(alignment, "texttop")) 484 verticalAlignValue = CSSValueTextTop; 485 486 if (floatValue != CSSValueInvalid) 487 addPropertyToPresentationAttributeStyle(style, CSSPropertyFloat, floatValue); 488 489 if (verticalAlignValue != CSSValueInvalid) 490 addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, verticalAlignValue); 491 } 492 493 bool HTMLElement::hasCustomFocusLogic() const 494 { 495 return false; 496 } 497 498 String HTMLElement::contentEditable() const 499 { 500 const AtomicString& value = fastGetAttribute(contenteditableAttr); 501 502 if (value.isNull()) 503 return "inherit"; 504 if (value.isEmpty() || equalIgnoringCase(value, "true")) 505 return "true"; 506 if (equalIgnoringCase(value, "false")) 507 return "false"; 508 if (equalIgnoringCase(value, "plaintext-only")) 509 return "plaintext-only"; 510 511 return "inherit"; 512 } 513 514 void HTMLElement::setContentEditable(const String& enabled, ExceptionState& exceptionState) 515 { 516 if (equalIgnoringCase(enabled, "true")) 517 setAttribute(contenteditableAttr, "true"); 518 else if (equalIgnoringCase(enabled, "false")) 519 setAttribute(contenteditableAttr, "false"); 520 else if (equalIgnoringCase(enabled, "plaintext-only")) 521 setAttribute(contenteditableAttr, "plaintext-only"); 522 else if (equalIgnoringCase(enabled, "inherit")) 523 removeAttribute(contenteditableAttr); 524 else 525 exceptionState.throwDOMException(SyntaxError, "The value provided ('" + enabled + "') is not one of 'true', 'false', 'plaintext-only', or 'inherit'."); 526 } 527 528 bool HTMLElement::draggable() const 529 { 530 return equalIgnoringCase(getAttribute(draggableAttr), "true"); 531 } 532 533 void HTMLElement::setDraggable(bool value) 534 { 535 setAttribute(draggableAttr, value ? "true" : "false"); 536 } 537 538 bool HTMLElement::spellcheck() const 539 { 540 return isSpellCheckingEnabled(); 541 } 542 543 void HTMLElement::setSpellcheck(bool enable) 544 { 545 setAttribute(spellcheckAttr, enable ? "true" : "false"); 546 } 547 548 549 void HTMLElement::click() 550 { 551 dispatchSimulatedClick(0, SendNoEvents); 552 } 553 554 void HTMLElement::accessKeyAction(bool sendMouseEvents) 555 { 556 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); 557 } 558 559 String HTMLElement::title() const 560 { 561 return fastGetAttribute(titleAttr); 562 } 563 564 short HTMLElement::tabIndex() const 565 { 566 if (supportsFocus()) 567 return Element::tabIndex(); 568 return -1; 569 } 570 571 TranslateAttributeMode HTMLElement::translateAttributeMode() const 572 { 573 const AtomicString& value = getAttribute(translateAttr); 574 575 if (value == nullAtom) 576 return TranslateAttributeInherit; 577 if (equalIgnoringCase(value, "yes") || equalIgnoringCase(value, "")) 578 return TranslateAttributeYes; 579 if (equalIgnoringCase(value, "no")) 580 return TranslateAttributeNo; 581 582 return TranslateAttributeInherit; 583 } 584 585 bool HTMLElement::translate() const 586 { 587 for (const HTMLElement* element = this; element; element = Traversal<HTMLElement>::firstAncestor(*element)) { 588 TranslateAttributeMode mode = element->translateAttributeMode(); 589 if (mode != TranslateAttributeInherit) { 590 ASSERT(mode == TranslateAttributeYes || mode == TranslateAttributeNo); 591 return mode == TranslateAttributeYes; 592 } 593 } 594 595 // Default on the root element is translate=yes. 596 return true; 597 } 598 599 void HTMLElement::setTranslate(bool enable) 600 { 601 setAttribute(translateAttr, enable ? "yes" : "no"); 602 } 603 604 // Returns the conforming 'dir' value associated with the state the attribute is in (in its canonical case), if any, 605 // or the empty string if the attribute is in a state that has no associated keyword value or if the attribute is 606 // not in a defined state (e.g. the attribute is missing and there is no missing value default). 607 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#limited-to-only-known-values 608 static inline const AtomicString& toValidDirValue(const AtomicString& value) 609 { 610 DEFINE_STATIC_LOCAL(const AtomicString, ltrValue, ("ltr", AtomicString::ConstructFromLiteral)); 611 DEFINE_STATIC_LOCAL(const AtomicString, rtlValue, ("rtl", AtomicString::ConstructFromLiteral)); 612 DEFINE_STATIC_LOCAL(const AtomicString, autoValue, ("auto", AtomicString::ConstructFromLiteral)); 613 614 if (equalIgnoringCase(value, ltrValue)) 615 return ltrValue; 616 if (equalIgnoringCase(value, rtlValue)) 617 return rtlValue; 618 if (equalIgnoringCase(value, autoValue)) 619 return autoValue; 620 return nullAtom; 621 } 622 623 const AtomicString& HTMLElement::dir() 624 { 625 return toValidDirValue(fastGetAttribute(dirAttr)); 626 } 627 628 void HTMLElement::setDir(const AtomicString& value) 629 { 630 setAttribute(dirAttr, value); 631 } 632 633 HTMLFormElement* HTMLElement::findFormAncestor() const 634 { 635 return Traversal<HTMLFormElement>::firstAncestor(*this); 636 } 637 638 static inline bool elementAffectsDirectionality(const Node* node) 639 { 640 return node->isHTMLElement() && (isHTMLBDIElement(toHTMLElement(*node)) || toHTMLElement(*node).hasAttribute(dirAttr)); 641 } 642 643 static void setHasDirAutoFlagRecursively(Node* firstNode, bool flag, Node* lastNode = 0) 644 { 645 firstNode->setSelfOrAncestorHasDirAutoAttribute(flag); 646 647 Node* node = firstNode->firstChild(); 648 649 while (node) { 650 if (elementAffectsDirectionality(node)) { 651 if (node == lastNode) 652 return; 653 node = NodeTraversal::nextSkippingChildren(*node, firstNode); 654 continue; 655 } 656 node->setSelfOrAncestorHasDirAutoAttribute(flag); 657 if (node == lastNode) 658 return; 659 node = NodeTraversal::next(*node, firstNode); 660 } 661 } 662 663 void HTMLElement::childrenChanged(const ChildrenChange& change) 664 { 665 Element::childrenChanged(change); 666 adjustDirectionalityIfNeededAfterChildrenChanged(change); 667 } 668 669 bool HTMLElement::hasDirectionAuto() const 670 { 671 const AtomicString& direction = fastGetAttribute(dirAttr); 672 return (isHTMLBDIElement(*this) && direction == nullAtom) || equalIgnoringCase(direction, "auto"); 673 } 674 675 TextDirection HTMLElement::directionalityIfhasDirAutoAttribute(bool& isAuto) const 676 { 677 if (!(selfOrAncestorHasDirAutoAttribute() && hasDirectionAuto())) { 678 isAuto = false; 679 return LTR; 680 } 681 682 isAuto = true; 683 return directionality(); 684 } 685 686 TextDirection HTMLElement::directionality(Node** strongDirectionalityTextNode) const 687 { 688 if (isHTMLInputElement(*this)) { 689 HTMLInputElement* inputElement = toHTMLInputElement(const_cast<HTMLElement*>(this)); 690 bool hasStrongDirectionality; 691 TextDirection textDirection = determineDirectionality(inputElement->value(), hasStrongDirectionality); 692 if (strongDirectionalityTextNode) 693 *strongDirectionalityTextNode = hasStrongDirectionality ? inputElement : 0; 694 return textDirection; 695 } 696 697 Node* node = firstChild(); 698 while (node) { 699 // Skip bdi, script, style and text form controls. 700 if (equalIgnoringCase(node->nodeName(), "bdi") || isHTMLScriptElement(*node) || isHTMLStyleElement(*node) 701 || (node->isElementNode() && toElement(node)->isTextFormControl())) { 702 node = NodeTraversal::nextSkippingChildren(*node, this); 703 continue; 704 } 705 706 // Skip elements with valid dir attribute 707 if (node->isElementNode()) { 708 AtomicString dirAttributeValue = toElement(node)->fastGetAttribute(dirAttr); 709 if (isValidDirAttribute(dirAttributeValue)) { 710 node = NodeTraversal::nextSkippingChildren(*node, this); 711 continue; 712 } 713 } 714 715 if (node->isTextNode()) { 716 bool hasStrongDirectionality; 717 TextDirection textDirection = determineDirectionality(node->textContent(true), hasStrongDirectionality); 718 if (hasStrongDirectionality) { 719 if (strongDirectionalityTextNode) 720 *strongDirectionalityTextNode = node; 721 return textDirection; 722 } 723 } 724 node = NodeTraversal::next(*node, this); 725 } 726 if (strongDirectionalityTextNode) 727 *strongDirectionalityTextNode = 0; 728 return LTR; 729 } 730 731 void HTMLElement::dirAttributeChanged(const AtomicString& value) 732 { 733 Element* parent = parentElement(); 734 735 if (parent && parent->isHTMLElement() && parent->selfOrAncestorHasDirAutoAttribute()) 736 toHTMLElement(parent)->adjustDirectionalityIfNeededAfterChildAttributeChanged(this); 737 738 if (equalIgnoringCase(value, "auto")) 739 calculateAndAdjustDirectionality(); 740 } 741 742 void HTMLElement::adjustDirectionalityIfNeededAfterChildAttributeChanged(Element* child) 743 { 744 ASSERT(selfOrAncestorHasDirAutoAttribute()); 745 Node* strongDirectionalityTextNode; 746 TextDirection textDirection = directionality(&strongDirectionalityTextNode); 747 setHasDirAutoFlagRecursively(child, false); 748 if (renderer() && renderer()->style() && renderer()->style()->direction() != textDirection) { 749 Element* elementToAdjust = this; 750 for (; elementToAdjust; elementToAdjust = elementToAdjust->parentElement()) { 751 if (elementAffectsDirectionality(elementToAdjust)) { 752 elementToAdjust->setNeedsStyleRecalc(SubtreeStyleChange); 753 return; 754 } 755 } 756 } 757 } 758 759 void HTMLElement::calculateAndAdjustDirectionality() 760 { 761 Node* strongDirectionalityTextNode; 762 TextDirection textDirection = directionality(&strongDirectionalityTextNode); 763 setHasDirAutoFlagRecursively(this, hasDirectionAuto(), strongDirectionalityTextNode); 764 for (ShadowRoot* root = youngestShadowRoot(); root; root = root->olderShadowRoot()) 765 setHasDirAutoFlagRecursively(root, hasDirectionAuto()); 766 if (renderer() && renderer()->style() && renderer()->style()->direction() != textDirection) 767 setNeedsStyleRecalc(SubtreeStyleChange); 768 } 769 770 void HTMLElement::adjustDirectionalityIfNeededAfterChildrenChanged(const ChildrenChange& change) 771 { 772 if (!selfOrAncestorHasDirAutoAttribute()) 773 return; 774 775 Node* oldMarkedNode = change.siblingBeforeChange ? NodeTraversal::nextSkippingChildren(*change.siblingBeforeChange) : 0; 776 while (oldMarkedNode && elementAffectsDirectionality(oldMarkedNode)) 777 oldMarkedNode = NodeTraversal::nextSkippingChildren(*oldMarkedNode, this); 778 if (oldMarkedNode) 779 setHasDirAutoFlagRecursively(oldMarkedNode, false); 780 781 for (Element* elementToAdjust = this; elementToAdjust; elementToAdjust = elementToAdjust->parentElement()) { 782 if (elementAffectsDirectionality(elementToAdjust)) { 783 toHTMLElement(elementToAdjust)->calculateAndAdjustDirectionality(); 784 return; 785 } 786 } 787 } 788 789 void HTMLElement::addHTMLLengthToStyle(MutableStylePropertySet* style, CSSPropertyID propertyID, const String& value) 790 { 791 // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct 792 // length unit and make the appropriate parsed value. 793 794 // strip attribute garbage.. 795 StringImpl* v = value.impl(); 796 if (v) { 797 unsigned length = 0; 798 799 while (length < v->length() && (*v)[length] <= ' ') 800 length++; 801 802 for (; length < v->length(); length++) { 803 UChar cc = (*v)[length]; 804 if (cc > '9') 805 break; 806 if (cc < '0') { 807 if (cc == '%' || cc == '*') 808 length++; 809 if (cc != '.') 810 break; 811 } 812 } 813 814 if (length != v->length()) { 815 addPropertyToPresentationAttributeStyle(style, propertyID, v->substring(0, length)); 816 return; 817 } 818 } 819 820 addPropertyToPresentationAttributeStyle(style, propertyID, value); 821 } 822 823 static RGBA32 parseColorStringWithCrazyLegacyRules(const String& colorString) 824 { 825 // Per spec, only look at the first 128 digits of the string. 826 const size_t maxColorLength = 128; 827 // We'll pad the buffer with two extra 0s later, so reserve two more than the max. 828 Vector<char, maxColorLength+2> digitBuffer; 829 830 size_t i = 0; 831 // Skip a leading #. 832 if (colorString[0] == '#') 833 i = 1; 834 835 // Grab the first 128 characters, replacing non-hex characters with 0. 836 // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String. 837 for (; i < colorString.length() && digitBuffer.size() < maxColorLength; i++) { 838 if (!isASCIIHexDigit(colorString[i])) 839 digitBuffer.append('0'); 840 else 841 digitBuffer.append(colorString[i]); 842 } 843 844 if (!digitBuffer.size()) 845 return Color::black; 846 847 // Pad the buffer out to at least the next multiple of three in size. 848 digitBuffer.append('0'); 849 digitBuffer.append('0'); 850 851 if (digitBuffer.size() < 6) 852 return makeRGB(toASCIIHexValue(digitBuffer[0]), toASCIIHexValue(digitBuffer[1]), toASCIIHexValue(digitBuffer[2])); 853 854 // Split the digits into three components, then search the last 8 digits of each component. 855 ASSERT(digitBuffer.size() >= 6); 856 size_t componentLength = digitBuffer.size() / 3; 857 size_t componentSearchWindowLength = min<size_t>(componentLength, 8); 858 size_t redIndex = componentLength - componentSearchWindowLength; 859 size_t greenIndex = componentLength * 2 - componentSearchWindowLength; 860 size_t blueIndex = componentLength * 3 - componentSearchWindowLength; 861 // Skip digits until one of them is non-zero, or we've only got two digits left in the component. 862 while (digitBuffer[redIndex] == '0' && digitBuffer[greenIndex] == '0' && digitBuffer[blueIndex] == '0' && (componentLength - redIndex) > 2) { 863 redIndex++; 864 greenIndex++; 865 blueIndex++; 866 } 867 ASSERT(redIndex + 1 < componentLength); 868 ASSERT(greenIndex >= componentLength); 869 ASSERT(greenIndex + 1 < componentLength * 2); 870 ASSERT(blueIndex >= componentLength * 2); 871 ASSERT_WITH_SECURITY_IMPLICATION(blueIndex + 1 < digitBuffer.size()); 872 873 int redValue = toASCIIHexValue(digitBuffer[redIndex], digitBuffer[redIndex + 1]); 874 int greenValue = toASCIIHexValue(digitBuffer[greenIndex], digitBuffer[greenIndex + 1]); 875 int blueValue = toASCIIHexValue(digitBuffer[blueIndex], digitBuffer[blueIndex + 1]); 876 return makeRGB(redValue, greenValue, blueValue); 877 } 878 879 // Color parsing that matches HTML's "rules for parsing a legacy color value" 880 void HTMLElement::addHTMLColorToStyle(MutableStylePropertySet* style, CSSPropertyID propertyID, const String& attributeValue) 881 { 882 // An empty string doesn't apply a color. (One containing only whitespace does, which is why this check occurs before stripping.) 883 if (attributeValue.isEmpty()) 884 return; 885 886 String colorString = attributeValue.stripWhiteSpace(); 887 888 // "transparent" doesn't apply a color either. 889 if (equalIgnoringCase(colorString, "transparent")) 890 return; 891 892 // If the string is a named CSS color or a 3/6-digit hex color, use that. 893 Color parsedColor; 894 if (!parsedColor.setFromString(colorString)) 895 parsedColor.setRGB(parseColorStringWithCrazyLegacyRules(colorString)); 896 897 style->setProperty(propertyID, cssValuePool().createColorValue(parsedColor.rgb())); 898 } 899 900 bool HTMLElement::isInteractiveContent() const 901 { 902 return false; 903 } 904 905 906 HTMLMenuElement* HTMLElement::contextMenu() const 907 { 908 const AtomicString& contextMenuId(fastGetAttribute(contextmenuAttr)); 909 if (contextMenuId.isNull()) 910 return nullptr; 911 912 Element* element = treeScope().getElementById(contextMenuId); 913 // Not checking if the menu element is of type "popup". 914 // Ignoring menu element type attribute is intentional according to the standard. 915 return isHTMLMenuElement(element) ? toHTMLMenuElement(element) : nullptr; 916 } 917 918 void HTMLElement::setContextMenu(HTMLMenuElement* contextMenu) 919 { 920 if (!contextMenu) { 921 setAttribute(contextmenuAttr, ""); 922 return; 923 } 924 925 // http://www.whatwg.org/specs/web-apps/current-work/multipage/infrastructure.html#reflecting-content-attributes-in-idl-attributes 926 // On setting, if the given element has an id attribute, and has the same home 927 // subtree as the element of the attribute being set, and the given element is the 928 // first element in that home subtree whose ID is the value of that id attribute, 929 // then the content attribute must be set to the value of that id attribute. 930 // Otherwise, the content attribute must be set to the empty string. 931 const AtomicString& contextMenuId(contextMenu->fastGetAttribute(idAttr)); 932 933 if (!contextMenuId.isNull() && contextMenu == treeScope().getElementById(contextMenuId)) 934 setAttribute(contextmenuAttr, contextMenuId); 935 else 936 setAttribute(contextmenuAttr, ""); 937 } 938 939 void HTMLElement::defaultEventHandler(Event* event) 940 { 941 if (event->type() == EventTypeNames::keypress && event->isKeyboardEvent()) { 942 handleKeypressEvent(toKeyboardEvent(event)); 943 if (event->defaultHandled()) 944 return; 945 } 946 947 Element::defaultEventHandler(event); 948 } 949 950 bool HTMLElement::matchesReadOnlyPseudoClass() const 951 { 952 return !matchesReadWritePseudoClass(); 953 } 954 955 bool HTMLElement::matchesReadWritePseudoClass() const 956 { 957 if (fastHasAttribute(contenteditableAttr)) { 958 const AtomicString& value = fastGetAttribute(contenteditableAttr); 959 960 if (value.isEmpty() || equalIgnoringCase(value, "true") || equalIgnoringCase(value, "plaintext-only")) 961 return true; 962 if (equalIgnoringCase(value, "false")) 963 return false; 964 // All other values should be treated as "inherit". 965 } 966 967 return parentElement() && parentElement()->hasEditableStyle(); 968 } 969 970 void HTMLElement::handleKeypressEvent(KeyboardEvent* event) 971 { 972 if (!document().settings() || !document().settings()->spatialNavigationEnabled() || !supportsFocus()) 973 return; 974 // if the element is a text form control (like <input type=text> or <textarea>) 975 // or has contentEditable attribute on, we should enter a space or newline 976 // even in spatial navigation mode instead of handling it as a "click" action. 977 if (isTextFormControl() || isContentEditable()) 978 return; 979 int charCode = event->charCode(); 980 if (charCode == '\r' || charCode == ' ') { 981 dispatchSimulatedClick(event); 982 event->setDefaultHandled(); 983 } 984 } 985 986 const AtomicString& HTMLElement::eventParameterName() 987 { 988 DEFINE_STATIC_LOCAL(const AtomicString, eventString, ("event", AtomicString::ConstructFromLiteral)); 989 return eventString; 990 } 991 992 } // namespace blink 993 994 #ifndef NDEBUG 995 996 // For use in the debugger 997 void dumpInnerHTML(blink::HTMLElement*); 998 999 void dumpInnerHTML(blink::HTMLElement* element) 1000 { 1001 printf("%s\n", element->innerHTML().ascii().data()); 1002 } 1003 #endif 1004