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