1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2004-2018 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 "HTMLElement.h"
27
28#include "CSSMarkup.h"
29#include "CSSPropertyNames.h"
30#include "CSSValueKeywords.h"
31#include "CSSValuePool.h"
32#include "Chrome.h"
33#include "ChromeClient.h"
34#include "DOMTokenList.h"
35#include "DocumentFragment.h"
36#include "ElementAncestorIterator.h"
37#include "Event.h"
38#include "EventListener.h"
39#include "EventNames.h"
40#include "Frame.h"
41#include "FrameLoader.h"
42#include "FrameView.h"
43#include "HTMLBDIElement.h"
44#include "HTMLBRElement.h"
45#include "HTMLButtonElement.h"
46#include "HTMLCollection.h"
47#include "HTMLDocument.h"
48#include "HTMLElementFactory.h"
49#include "HTMLFieldSetElement.h"
50#include "HTMLFormElement.h"
51#include "HTMLInputElement.h"
52#include "HTMLNames.h"
53#include "HTMLOptGroupElement.h"
54#include "HTMLOptionElement.h"
55#include "HTMLParserIdioms.h"
56#include "HTMLSelectElement.h"
57#include "HTMLTextAreaElement.h"
58#include "HTMLTextFormControlElement.h"
59#include "NodeTraversal.h"
60#include "RenderElement.h"
61#include "ScriptController.h"
62#include "ShadowRoot.h"
63#include "SimulatedClick.h"
64#include "StyleProperties.h"
65#include "SubframeLoader.h"
66#include "Text.h"
67#include "XMLNames.h"
68#include "markup.h"
69#include <wtf/IsoMallocInlines.h>
70#include <wtf/NeverDestroyed.h>
71#include <wtf/StdLibExtras.h>
72#include <wtf/text/CString.h>
73
74namespace WebCore {
75
76WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLElement);
77
78using namespace HTMLNames;
79
80Ref<HTMLElement> HTMLElement::create(const QualifiedName& tagName, Document& document)
81{
82 return adoptRef(*new HTMLElement(tagName, document));
83}
84
85String HTMLElement::nodeName() const
86{
87 // FIXME: Would be nice to have an AtomicString lookup based off uppercase
88 // ASCII characters that does not have to copy the string on a hit in the hash.
89 if (document().isHTMLDocument()) {
90 if (LIKELY(!tagQName().hasPrefix()))
91 return tagQName().localNameUpper();
92 return Element::nodeName().convertToASCIIUppercase();
93 }
94 return Element::nodeName();
95}
96
97static inline CSSValueID unicodeBidiAttributeForDirAuto(HTMLElement& element)
98{
99 if (element.hasTagName(preTag) || element.hasTagName(textareaTag))
100 return CSSValuePlaintext;
101 // FIXME: For bdo element, dir="auto" should result in "bidi-override isolate" but we don't support having multiple values in unicode-bidi yet.
102 // See https://bugs.webkit.org/show_bug.cgi?id=73164.
103 return CSSValueIsolate;
104}
105
106unsigned HTMLElement::parseBorderWidthAttribute(const AtomicString& value) const
107{
108 if (auto optionalBorderWidth = parseHTMLNonNegativeInteger(value))
109 return optionalBorderWidth.value();
110
111 return hasTagName(tableTag) ? 1 : 0;
112}
113
114void HTMLElement::applyBorderAttributeToStyle(const AtomicString& value, MutableStyleProperties& style)
115{
116 addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderWidth, parseBorderWidthAttribute(value), CSSPrimitiveValue::CSS_PX);
117 addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderStyle, CSSValueSolid);
118}
119
120void HTMLElement::mapLanguageAttributeToLocale(const AtomicString& value, MutableStyleProperties& style)
121{
122 if (!value.isEmpty()) {
123 // Have to quote so the locale id is treated as a string instead of as a CSS keyword.
124 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLocale, serializeString(value));
125 } else {
126 // The empty string means the language is explicitly unknown.
127 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLocale, CSSValueAuto);
128 }
129}
130
131bool HTMLElement::isPresentationAttribute(const QualifiedName& name) const
132{
133 if (name == alignAttr || name == contenteditableAttr || name == hiddenAttr || name == langAttr || name.matches(XMLNames::langAttr) || name == draggableAttr || name == dirAttr)
134 return true;
135 return StyledElement::isPresentationAttribute(name);
136}
137
138static bool isLTROrRTLIgnoringCase(const AtomicString& dirAttributeValue)
139{
140 return equalLettersIgnoringASCIICase(dirAttributeValue, "rtl") || equalLettersIgnoringASCIICase(dirAttributeValue, "ltr");
141}
142
143enum class ContentEditableType {
144 Inherit,
145 True,
146 False,
147 PlaintextOnly
148};
149
150static inline ContentEditableType contentEditableType(const AtomicString& value)
151{
152 if (value.isNull())
153 return ContentEditableType::Inherit;
154 if (value.isEmpty() || equalLettersIgnoringASCIICase(value, "true"))
155 return ContentEditableType::True;
156 if (equalLettersIgnoringASCIICase(value, "false"))
157 return ContentEditableType::False;
158 if (equalLettersIgnoringASCIICase(value, "plaintext-only"))
159 return ContentEditableType::PlaintextOnly;
160
161 return ContentEditableType::Inherit;
162}
163
164static ContentEditableType contentEditableType(const HTMLElement& element)
165{
166 return contentEditableType(element.attributeWithoutSynchronization(contenteditableAttr));
167}
168
169void HTMLElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
170{
171 if (name == alignAttr) {
172 if (equalLettersIgnoringASCIICase(value, "middle"))
173 addPropertyToPresentationAttributeStyle(style, CSSPropertyTextAlign, CSSValueCenter);
174 else
175 addPropertyToPresentationAttributeStyle(style, CSSPropertyTextAlign, value);
176 } else if (name == contenteditableAttr) {
177 CSSValueID userModifyValue = CSSValueReadWrite;
178 switch (contentEditableType(value)) {
179 case ContentEditableType::Inherit:
180 return;
181 case ContentEditableType::False:
182 userModifyValue = CSSValueReadOnly;
183 break;
184 case ContentEditableType::PlaintextOnly:
185 userModifyValue = CSSValueReadWritePlaintextOnly;
186 FALLTHROUGH;
187 case ContentEditableType::True:
188 addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord);
189 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitNbspMode, CSSValueSpace);
190 addPropertyToPresentationAttributeStyle(style, CSSPropertyLineBreak, CSSValueAfterWhiteSpace);
191#if PLATFORM(IOS_FAMILY)
192 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitTextSizeAdjust, CSSValueNone);
193#endif
194 break;
195 }
196 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserModify, userModifyValue);
197 } else if (name == hiddenAttr) {
198 addPropertyToPresentationAttributeStyle(style, CSSPropertyDisplay, CSSValueNone);
199 } else if (name == draggableAttr) {
200 if (equalLettersIgnoringASCIICase(value, "true")) {
201 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserDrag, CSSValueElement);
202 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserSelect, CSSValueNone);
203 } else if (equalLettersIgnoringASCIICase(value, "false"))
204 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitUserDrag, CSSValueNone);
205 } else if (name == dirAttr) {
206 if (equalLettersIgnoringASCIICase(value, "auto"))
207 addPropertyToPresentationAttributeStyle(style, CSSPropertyUnicodeBidi, unicodeBidiAttributeForDirAuto(*this));
208 else {
209 if (isLTROrRTLIgnoringCase(value))
210 addPropertyToPresentationAttributeStyle(style, CSSPropertyDirection, value);
211 if (!hasTagName(bdiTag) && !hasTagName(bdoTag) && !hasTagName(outputTag))
212 addPropertyToPresentationAttributeStyle(style, CSSPropertyUnicodeBidi, CSSValueEmbed);
213 }
214 } else if (name.matches(XMLNames::langAttr))
215 mapLanguageAttributeToLocale(value, style);
216 else if (name == langAttr) {
217 // xml:lang has a higher priority than lang.
218 if (!hasAttributeWithoutSynchronization(XMLNames::langAttr))
219 mapLanguageAttributeToLocale(value, style);
220 } else
221 StyledElement::collectStyleForPresentationAttribute(name, value, style);
222}
223
224HTMLElement::EventHandlerNameMap HTMLElement::createEventHandlerNameMap()
225{
226 EventHandlerNameMap map;
227
228 static const QualifiedName* const table[] = {
229 &onabortAttr.get(),
230 &onanimationendAttr.get(),
231 &onanimationiterationAttr.get(),
232 &onanimationstartAttr.get(),
233 &onanimationcancelAttr.get(),
234 &onautocompleteAttr.get(),
235 &onautocompleteerrorAttr.get(),
236 &onbeforecopyAttr.get(),
237 &onbeforecutAttr.get(),
238 &onbeforeinputAttr.get(),
239 &onbeforeloadAttr.get(),
240 &onbeforepasteAttr.get(),
241 &onblurAttr.get(),
242 &oncanplayAttr.get(),
243 &oncanplaythroughAttr.get(),
244 &onchangeAttr.get(),
245 &onclickAttr.get(),
246 &oncontextmenuAttr.get(),
247 &oncopyAttr.get(),
248 &oncutAttr.get(),
249 &ondblclickAttr.get(),
250 &ondragAttr.get(),
251 &ondragendAttr.get(),
252 &ondragenterAttr.get(),
253 &ondragleaveAttr.get(),
254 &ondragoverAttr.get(),
255 &ondragstartAttr.get(),
256 &ondropAttr.get(),
257 &ondurationchangeAttr.get(),
258 &onemptiedAttr.get(),
259 &onendedAttr.get(),
260 &onerrorAttr.get(),
261 &onfocusAttr.get(),
262 &onfocusinAttr.get(),
263 &onfocusoutAttr.get(),
264 &ongesturechangeAttr.get(),
265 &ongestureendAttr.get(),
266 &ongesturestartAttr.get(),
267 &ongotpointercaptureAttr.get(),
268 &oninputAttr.get(),
269 &oninvalidAttr.get(),
270 &onkeydownAttr.get(),
271 &onkeypressAttr.get(),
272 &onkeyupAttr.get(),
273 &onloadAttr.get(),
274 &onloadeddataAttr.get(),
275 &onloadedmetadataAttr.get(),
276 &onloadstartAttr.get(),
277 &onlostpointercaptureAttr.get(),
278 &onmousedownAttr.get(),
279 &onmouseenterAttr.get(),
280 &onmouseleaveAttr.get(),
281 &onmousemoveAttr.get(),
282 &onmouseoutAttr.get(),
283 &onmouseoverAttr.get(),
284 &onmouseupAttr.get(),
285 &onmousewheelAttr.get(),
286 &onpasteAttr.get(),
287 &onpauseAttr.get(),
288 &onplayAttr.get(),
289 &onplayingAttr.get(),
290 &onpointerdownAttr.get(),
291 &onpointermoveAttr.get(),
292 &onpointerupAttr.get(),
293 &onpointercancelAttr.get(),
294 &onpointeroverAttr.get(),
295 &onpointeroutAttr.get(),
296 &onpointerenterAttr.get(),
297 &onpointerleaveAttr.get(),
298 &onprogressAttr.get(),
299 &onratechangeAttr.get(),
300 &onresetAttr.get(),
301 &onresizeAttr.get(),
302 &onscrollAttr.get(),
303 &onsearchAttr.get(),
304 &onseekedAttr.get(),
305 &onseekingAttr.get(),
306 &onselectAttr.get(),
307 &onselectstartAttr.get(),
308 &onstalledAttr.get(),
309 &onsubmitAttr.get(),
310 &onsuspendAttr.get(),
311 &ontimeupdateAttr.get(),
312 &ontoggleAttr.get(),
313 &ontouchcancelAttr.get(),
314 &ontouchendAttr.get(),
315 &ontouchforcechangeAttr.get(),
316 &ontouchmoveAttr.get(),
317 &ontouchstartAttr.get(),
318 &ontransitioncancelAttr.get(),
319 &ontransitionendAttr.get(),
320 &ontransitionrunAttr.get(),
321 &ontransitionstartAttr.get(),
322 &onvolumechangeAttr.get(),
323 &onwaitingAttr.get(),
324 &onwebkitbeginfullscreenAttr.get(),
325 &onwebkitcurrentplaybacktargetiswirelesschangedAttr.get(),
326 &onwebkitendfullscreenAttr.get(),
327 &onwebkitfullscreenchangeAttr.get(),
328 &onwebkitfullscreenerrorAttr.get(),
329 &onwebkitkeyaddedAttr.get(),
330 &onwebkitkeyerrorAttr.get(),
331 &onwebkitkeymessageAttr.get(),
332 &onwebkitmouseforcechangedAttr.get(),
333 &onwebkitmouseforcedownAttr.get(),
334 &onwebkitmouseforcewillbeginAttr.get(),
335 &onwebkitmouseforceupAttr.get(),
336 &onwebkitneedkeyAttr.get(),
337 &onwebkitplaybacktargetavailabilitychangedAttr.get(),
338 &onwebkitpresentationmodechangedAttr.get(),
339 &onwebkitwillrevealbottomAttr.get(),
340 &onwebkitwillrevealleftAttr.get(),
341 &onwebkitwillrevealrightAttr.get(),
342 &onwebkitwillrevealtopAttr.get(),
343 &onwheelAttr.get(),
344 };
345
346 populateEventHandlerNameMap(map, table);
347
348 struct UnusualMapping {
349 const QualifiedName& attributeName;
350 const AtomicString& eventName;
351 };
352
353 const UnusualMapping unusualPairsTable[] = {
354 { onwebkitanimationendAttr, eventNames().webkitAnimationEndEvent },
355 { onwebkitanimationiterationAttr, eventNames().webkitAnimationIterationEvent },
356 { onwebkitanimationstartAttr, eventNames().webkitAnimationStartEvent },
357 { onwebkittransitionendAttr, eventNames().webkitTransitionEndEvent },
358 };
359
360 for (auto& entry : unusualPairsTable)
361 map.add(entry.attributeName.localName().impl(), entry.eventName);
362
363 return map;
364}
365
366void HTMLElement::populateEventHandlerNameMap(EventHandlerNameMap& map, const QualifiedName* const table[], size_t tableSize)
367{
368 for (size_t i = 0; i < tableSize; ++i) {
369 auto* entry = table[i];
370
371 // FIXME: Would be nice to check these against the actual event names in eventNames().
372 // Not obvious how to do that simply, though.
373 auto& attributeName = entry->localName();
374
375 // Remove the "on" prefix. Requires some memory allocation and computing a hash, but by not
376 // using pointers from eventNames(), the passed-in table can be initialized at compile time.
377 AtomicString eventName = attributeName.string().substring(2);
378
379 map.add(attributeName.impl(), WTFMove(eventName));
380 }
381}
382
383const AtomicString& HTMLElement::eventNameForEventHandlerAttribute(const QualifiedName& attributeName, const EventHandlerNameMap& map)
384{
385 ASSERT(!attributeName.localName().isNull());
386
387 // Event handler attributes have no namespace.
388 if (!attributeName.namespaceURI().isNull())
389 return nullAtom();
390
391 // Fast early return for names that don't start with "on".
392 AtomicStringImpl& localName = *attributeName.localName().impl();
393 if (localName.length() < 3 || localName[0] != 'o' || localName[1] != 'n')
394 return nullAtom();
395
396 auto it = map.find(&localName);
397 return it == map.end() ? nullAtom() : it->value;
398}
399
400const AtomicString& HTMLElement::eventNameForEventHandlerAttribute(const QualifiedName& attributeName)
401{
402 static NeverDestroyed<EventHandlerNameMap> map = createEventHandlerNameMap();
403 return eventNameForEventHandlerAttribute(attributeName, map.get());
404}
405
406Node::Editability HTMLElement::editabilityFromContentEditableAttr(const Node& node)
407{
408 if (auto* startElement = is<Element>(node) ? &downcast<Element>(node) : node.parentElement()) {
409 for (auto& element : lineageOfType<HTMLElement>(*startElement)) {
410 switch (contentEditableType(element)) {
411 case ContentEditableType::True:
412 return Editability::CanEditRichly;
413 case ContentEditableType::PlaintextOnly:
414 return Editability::CanEditPlainText;
415 case ContentEditableType::False:
416 return Editability::ReadOnly;
417 case ContentEditableType::Inherit:
418 break;
419 }
420 }
421 }
422
423 auto containingShadowRoot = makeRefPtr(node.containingShadowRoot());
424 if (containingShadowRoot && containingShadowRoot->mode() == ShadowRootMode::UserAgent)
425 return Editability::ReadOnly;
426
427 auto& document = node.document();
428 if (is<HTMLDocument>(document))
429 return downcast<HTMLDocument>(document).inDesignMode() ? Editability::CanEditRichly : Editability::ReadOnly;
430
431 return Editability::ReadOnly;
432}
433
434bool HTMLElement::matchesReadWritePseudoClass() const
435{
436 return editabilityFromContentEditableAttr(*this) != Editability::ReadOnly;
437}
438
439void HTMLElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
440{
441 if (name == dirAttr) {
442 dirAttributeChanged(value);
443 return;
444 }
445
446 if (name == tabindexAttr) {
447 if (value.isEmpty())
448 clearTabIndexExplicitlyIfNeeded();
449 else if (auto optionalTabIndex = parseHTMLInteger(value))
450 setTabIndexExplicitly(optionalTabIndex.value());
451 return;
452 }
453
454 if (name == inputmodeAttr) {
455 auto& document = this->document();
456 if (this == document.focusedElement()) {
457 if (auto* page = document.page())
458 page->chrome().client().focusedElementDidChangeInputMode(*this, canonicalInputMode());
459 }
460 }
461
462 auto& eventName = eventNameForEventHandlerAttribute(name);
463 if (!eventName.isNull())
464 setAttributeEventListener(eventName, name, value);
465}
466
467static Ref<DocumentFragment> textToFragment(Document& document, const String& text)
468{
469 auto fragment = DocumentFragment::create(document);
470
471 // It's safe to dispatch events on the new fragment since author scripts have no access to it yet.
472 ScriptDisallowedScope::EventAllowedScope allowedScope(fragment);
473
474 for (unsigned start = 0, length = text.length(); start < length; ) {
475 // Find next line break.
476 UChar c = 0;
477 unsigned i;
478 for (i = start; i < length; i++) {
479 c = text[i];
480 if (c == '\r' || c == '\n')
481 break;
482 }
483
484 // If text is not the empty string, then append a new Text node whose data is text and node document is document to fragment.
485 if (i > start)
486 fragment->appendChild(Text::create(document, text.substring(start, i - start)));
487
488 if (i == length)
489 break;
490
491 fragment->appendChild(HTMLBRElement::create(document));
492 // Make sure \r\n doesn't result in two line breaks.
493 if (c == '\r' && i + 1 < length && text[i + 1] == '\n')
494 ++i;
495
496 start = i + 1; // Character after line break.
497 }
498
499 return fragment;
500}
501
502// Returns the conforming 'dir' value associated with the state the attribute is in (in its canonical case), if any,
503// or the empty string if the attribute is in a state that has no associated keyword value or if the attribute is
504// not in a defined state (e.g. the attribute is missing and there is no missing value default).
505// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#limited-to-only-known-values
506static inline const AtomicString& toValidDirValue(const AtomicString& value)
507{
508 static NeverDestroyed<AtomicString> ltrValue("ltr", AtomicString::ConstructFromLiteral);
509 static NeverDestroyed<AtomicString> rtlValue("rtl", AtomicString::ConstructFromLiteral);
510 static NeverDestroyed<AtomicString> autoValue("auto", AtomicString::ConstructFromLiteral);
511 if (equalLettersIgnoringASCIICase(value, "ltr"))
512 return ltrValue;
513 if (equalLettersIgnoringASCIICase(value, "rtl"))
514 return rtlValue;
515 if (equalLettersIgnoringASCIICase(value, "auto"))
516 return autoValue;
517 return nullAtom();
518}
519
520const AtomicString& HTMLElement::dir() const
521{
522 return toValidDirValue(attributeWithoutSynchronization(dirAttr));
523}
524
525void HTMLElement::setDir(const AtomicString& value)
526{
527 setAttributeWithoutSynchronization(dirAttr, value);
528}
529
530ExceptionOr<void> HTMLElement::setInnerText(const String& text)
531{
532 // FIXME: This doesn't take whitespace collapsing into account at all.
533
534 if (!text.contains('\n') && !text.contains('\r')) {
535 if (text.isEmpty())
536 replaceAllChildren(nullptr);
537 else
538 replaceAllChildren(document().createTextNode(text));
539 return { };
540 }
541
542 // FIXME: Do we need to be able to detect preserveNewline style even when there's no renderer?
543 // FIXME: Can the renderer be out of date here? Do we need to call updateStyleIfNeeded?
544 // For example, for the contents of textarea elements that are display:none?
545 auto* r = renderer();
546 if ((r && r->style().preserveNewline()) || (isConnected() && isTextControlInnerTextElement())) {
547 if (!text.contains('\r')) {
548 replaceAllChildren(document().createTextNode(text));
549 return { };
550 }
551 String textWithConsistentLineBreaks = text;
552 textWithConsistentLineBreaks.replace("\r\n", "\n");
553 textWithConsistentLineBreaks.replace('\r', '\n');
554 replaceAllChildren(document().createTextNode(textWithConsistentLineBreaks));
555 return { };
556 }
557
558 // Add text nodes and <br> elements.
559 auto fragment = textToFragment(document(), text);
560 // FIXME: This should use replaceAllChildren() once it accepts DocumentFragments as input.
561 // It's safe to dispatch events on the new fragment since author scripts have no access to it yet.
562 ScriptDisallowedScope::EventAllowedScope allowedScope(fragment.get());
563 return replaceChildrenWithFragment(*this, WTFMove(fragment));
564}
565
566ExceptionOr<void> HTMLElement::setOuterText(const String& text)
567{
568 RefPtr<ContainerNode> parent = parentNode();
569 if (!parent)
570 return Exception { NoModificationAllowedError };
571
572 RefPtr<Node> prev = previousSibling();
573 RefPtr<Node> next = nextSibling();
574 RefPtr<Node> newChild;
575
576 // Convert text to fragment with <br> tags instead of linebreaks if needed.
577 if (text.contains('\r') || text.contains('\n'))
578 newChild = textToFragment(document(), text);
579 else
580 newChild = Text::create(document(), text);
581
582 if (!parentNode())
583 return Exception { HierarchyRequestError };
584
585 auto replaceResult = parent->replaceChild(*newChild, *this);
586 if (replaceResult.hasException())
587 return replaceResult.releaseException();
588
589 RefPtr<Node> node = next ? next->previousSibling() : nullptr;
590 if (is<Text>(node)) {
591 auto result = mergeWithNextTextNode(downcast<Text>(*node));
592 if (result.hasException())
593 return result.releaseException();
594 }
595 if (is<Text>(prev)) {
596 auto result = mergeWithNextTextNode(downcast<Text>(*prev));
597 if (result.hasException())
598 return result.releaseException();
599 }
600 return { };
601}
602
603void HTMLElement::applyAlignmentAttributeToStyle(const AtomicString& alignment, MutableStyleProperties& style)
604{
605 // Vertical alignment with respect to the current baseline of the text
606 // right or left means floating images.
607 CSSValueID floatValue = CSSValueInvalid;
608 CSSValueID verticalAlignValue = CSSValueInvalid;
609
610 if (equalLettersIgnoringASCIICase(alignment, "absmiddle"))
611 verticalAlignValue = CSSValueMiddle;
612 else if (equalLettersIgnoringASCIICase(alignment, "absbottom"))
613 verticalAlignValue = CSSValueBottom;
614 else if (equalLettersIgnoringASCIICase(alignment, "left")) {
615 floatValue = CSSValueLeft;
616 verticalAlignValue = CSSValueTop;
617 } else if (equalLettersIgnoringASCIICase(alignment, "right")) {
618 floatValue = CSSValueRight;
619 verticalAlignValue = CSSValueTop;
620 } else if (equalLettersIgnoringASCIICase(alignment, "top"))
621 verticalAlignValue = CSSValueTop;
622 else if (equalLettersIgnoringASCIICase(alignment, "middle"))
623 verticalAlignValue = CSSValueWebkitBaselineMiddle;
624 else if (equalLettersIgnoringASCIICase(alignment, "center"))
625 verticalAlignValue = CSSValueMiddle;
626 else if (equalLettersIgnoringASCIICase(alignment, "bottom"))
627 verticalAlignValue = CSSValueBaseline;
628 else if (equalLettersIgnoringASCIICase(alignment, "texttop"))
629 verticalAlignValue = CSSValueTextTop;
630
631 if (floatValue != CSSValueInvalid)
632 addPropertyToPresentationAttributeStyle(style, CSSPropertyFloat, floatValue);
633
634 if (verticalAlignValue != CSSValueInvalid)
635 addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, verticalAlignValue);
636}
637
638bool HTMLElement::hasCustomFocusLogic() const
639{
640 return false;
641}
642
643bool HTMLElement::supportsFocus() const
644{
645 return Element::supportsFocus() || (hasEditableStyle() && parentNode() && !parentNode()->hasEditableStyle());
646}
647
648String HTMLElement::contentEditable() const
649{
650 switch (contentEditableType(*this)) {
651 case ContentEditableType::Inherit:
652 return "inherit"_s;
653 case ContentEditableType::True:
654 return "true"_s;
655 case ContentEditableType::False:
656 return "false"_s;
657 case ContentEditableType::PlaintextOnly:
658 return "plaintext-only"_s;
659 }
660 return "inherit"_s;
661}
662
663ExceptionOr<void> HTMLElement::setContentEditable(const String& enabled)
664{
665 if (equalLettersIgnoringASCIICase(enabled, "true"))
666 setAttributeWithoutSynchronization(contenteditableAttr, AtomicString("true", AtomicString::ConstructFromLiteral));
667 else if (equalLettersIgnoringASCIICase(enabled, "false"))
668 setAttributeWithoutSynchronization(contenteditableAttr, AtomicString("false", AtomicString::ConstructFromLiteral));
669 else if (equalLettersIgnoringASCIICase(enabled, "plaintext-only"))
670 setAttributeWithoutSynchronization(contenteditableAttr, AtomicString("plaintext-only", AtomicString::ConstructFromLiteral));
671 else if (equalLettersIgnoringASCIICase(enabled, "inherit"))
672 removeAttribute(contenteditableAttr);
673 else
674 return Exception { SyntaxError };
675 return { };
676}
677
678bool HTMLElement::draggable() const
679{
680 return equalLettersIgnoringASCIICase(attributeWithoutSynchronization(draggableAttr), "true");
681}
682
683void HTMLElement::setDraggable(bool value)
684{
685 setAttributeWithoutSynchronization(draggableAttr, value
686 ? AtomicString("true", AtomicString::ConstructFromLiteral)
687 : AtomicString("false", AtomicString::ConstructFromLiteral));
688}
689
690bool HTMLElement::spellcheck() const
691{
692 return isSpellCheckingEnabled();
693}
694
695void HTMLElement::setSpellcheck(bool enable)
696{
697 setAttributeWithoutSynchronization(spellcheckAttr, enable
698 ? AtomicString("true", AtomicString::ConstructFromLiteral)
699 : AtomicString("false", AtomicString::ConstructFromLiteral));
700}
701
702void HTMLElement::click()
703{
704 simulateClick(*this, nullptr, SendNoEvents, DoNotShowPressedLook, SimulatedClickSource::Bindings);
705}
706
707void HTMLElement::accessKeyAction(bool sendMouseEvents)
708{
709 dispatchSimulatedClick(nullptr, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
710}
711
712String HTMLElement::title() const
713{
714 return attributeWithoutSynchronization(titleAttr);
715}
716
717int HTMLElement::tabIndex() const
718{
719 if (supportsFocus())
720 return Element::tabIndex();
721 return -1;
722}
723
724bool HTMLElement::translate() const
725{
726 for (auto& element : lineageOfType<HTMLElement>(*this)) {
727 const AtomicString& value = element.attributeWithoutSynchronization(translateAttr);
728 if (equalLettersIgnoringASCIICase(value, "yes") || (value.isEmpty() && !value.isNull()))
729 return true;
730 if (equalLettersIgnoringASCIICase(value, "no"))
731 return false;
732 }
733 // Default on the root element is translate=yes.
734 return true;
735}
736
737void HTMLElement::setTranslate(bool enable)
738{
739 setAttributeWithoutSynchronization(translateAttr, enable ? "yes" : "no");
740}
741
742bool HTMLElement::rendererIsNeeded(const RenderStyle& style)
743{
744 if (hasTagName(noscriptTag)) {
745 RefPtr<Frame> frame = document().frame();
746 if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript))
747 return false;
748 } else if (hasTagName(noembedTag)) {
749 RefPtr<Frame> frame = document().frame();
750 if (frame && frame->loader().subframeLoader().allowPlugins())
751 return false;
752 }
753 return StyledElement::rendererIsNeeded(style);
754}
755
756RenderPtr<RenderElement> HTMLElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
757{
758 return RenderElement::createFor(*this, WTFMove(style));
759}
760
761HTMLFormElement* HTMLElement::form() const
762{
763 return HTMLFormElement::findClosestFormAncestor(*this);
764}
765
766FormNamedItem* HTMLElement::asFormNamedItem()
767{
768 return nullptr;
769}
770
771FormAssociatedElement* HTMLElement::asFormAssociatedElement()
772{
773 return nullptr;
774}
775
776static inline bool elementAffectsDirectionality(const Node& node)
777{
778 if (!is<HTMLElement>(node))
779 return false;
780 const HTMLElement& element = downcast<HTMLElement>(node);
781 return is<HTMLBDIElement>(element) || element.hasAttributeWithoutSynchronization(dirAttr);
782}
783
784static void setHasDirAutoFlagRecursively(Node* firstNode, bool flag, Node* lastNode = nullptr)
785{
786 firstNode->setSelfOrAncestorHasDirAutoAttribute(flag);
787
788 RefPtr<Node> node = firstNode->firstChild();
789
790 while (node) {
791 if (node->selfOrAncestorHasDirAutoAttribute() == flag)
792 return;
793
794 if (elementAffectsDirectionality(*node)) {
795 if (node == lastNode)
796 return;
797 node = NodeTraversal::nextSkippingChildren(*node, firstNode);
798 continue;
799 }
800 node->setSelfOrAncestorHasDirAutoAttribute(flag);
801 if (node == lastNode)
802 return;
803 node = NodeTraversal::next(*node, firstNode);
804 }
805}
806
807void HTMLElement::childrenChanged(const ChildChange& change)
808{
809 StyledElement::childrenChanged(change);
810 adjustDirectionalityIfNeededAfterChildrenChanged(change.previousSiblingElement, change.type);
811}
812
813bool HTMLElement::hasDirectionAuto() const
814{
815 const AtomicString& direction = attributeWithoutSynchronization(dirAttr);
816 return (hasTagName(bdiTag) && direction.isNull()) || equalLettersIgnoringASCIICase(direction, "auto");
817}
818
819TextDirection HTMLElement::directionalityIfhasDirAutoAttribute(bool& isAuto) const
820{
821 if (!(selfOrAncestorHasDirAutoAttribute() && hasDirectionAuto())) {
822 isAuto = false;
823 return TextDirection::LTR;
824 }
825
826 isAuto = true;
827 return directionality();
828}
829
830TextDirection HTMLElement::directionality(Node** strongDirectionalityTextNode) const
831{
832 if (isTextField()) {
833 HTMLTextFormControlElement& textElement = downcast<HTMLTextFormControlElement>(const_cast<HTMLElement&>(*this));
834 bool hasStrongDirectionality;
835 UCharDirection textDirection = textElement.value().defaultWritingDirection(&hasStrongDirectionality);
836 if (strongDirectionalityTextNode)
837 *strongDirectionalityTextNode = hasStrongDirectionality ? &textElement : nullptr;
838 return (textDirection == U_LEFT_TO_RIGHT) ? TextDirection::LTR : TextDirection::RTL;
839 }
840
841 RefPtr<Node> node = firstChild();
842 while (node) {
843 // Skip bdi, script, style and text form controls.
844 if (equalLettersIgnoringASCIICase(node->nodeName(), "bdi") || node->hasTagName(scriptTag) || node->hasTagName(styleTag)
845 || (is<Element>(*node) && downcast<Element>(*node).isTextField())) {
846 node = NodeTraversal::nextSkippingChildren(*node, this);
847 continue;
848 }
849
850 // Skip elements with valid dir attribute
851 if (is<Element>(*node)) {
852 auto& dirAttributeValue = downcast<Element>(*node).attributeWithoutSynchronization(dirAttr);
853 if (isLTROrRTLIgnoringCase(dirAttributeValue) || equalLettersIgnoringASCIICase(dirAttributeValue, "auto")) {
854 node = NodeTraversal::nextSkippingChildren(*node, this);
855 continue;
856 }
857 }
858
859 if (node->isTextNode()) {
860 bool hasStrongDirectionality;
861 UCharDirection textDirection = node->textContent(true).defaultWritingDirection(&hasStrongDirectionality);
862 if (hasStrongDirectionality) {
863 if (strongDirectionalityTextNode)
864 *strongDirectionalityTextNode = node.get();
865 return (textDirection == U_LEFT_TO_RIGHT) ? TextDirection::LTR : TextDirection::RTL;
866 }
867 }
868 node = NodeTraversal::next(*node, this);
869 }
870 if (strongDirectionalityTextNode)
871 *strongDirectionalityTextNode = nullptr;
872 return TextDirection::LTR;
873}
874
875void HTMLElement::dirAttributeChanged(const AtomicString& value)
876{
877 RefPtr<Element> parent = parentElement();
878
879 if (is<HTMLElement>(parent) && parent->selfOrAncestorHasDirAutoAttribute())
880 downcast<HTMLElement>(*parent).adjustDirectionalityIfNeededAfterChildAttributeChanged(this);
881
882 if (equalLettersIgnoringASCIICase(value, "auto"))
883 calculateAndAdjustDirectionality();
884}
885
886void HTMLElement::adjustDirectionalityIfNeededAfterChildAttributeChanged(Element* child)
887{
888 ASSERT(selfOrAncestorHasDirAutoAttribute());
889 Node* strongDirectionalityTextNode;
890 TextDirection textDirection = directionality(&strongDirectionalityTextNode);
891 setHasDirAutoFlagRecursively(child, false);
892 if (!renderer() || renderer()->style().direction() == textDirection)
893 return;
894 for (auto& elementToAdjust : elementLineage(this)) {
895 if (elementAffectsDirectionality(elementToAdjust)) {
896 elementToAdjust.invalidateStyleForSubtree();
897 return;
898 }
899 }
900}
901
902void HTMLElement::calculateAndAdjustDirectionality()
903{
904 Node* strongDirectionalityTextNode;
905 TextDirection textDirection = directionality(&strongDirectionalityTextNode);
906 setHasDirAutoFlagRecursively(this, true, strongDirectionalityTextNode);
907 if (renderer() && renderer()->style().direction() != textDirection)
908 invalidateStyleForSubtree();
909}
910
911void HTMLElement::adjustDirectionalityIfNeededAfterChildrenChanged(Element* beforeChange, ChildChangeType changeType)
912{
913 // FIXME: This function looks suspicious.
914
915 if (!selfOrAncestorHasDirAutoAttribute())
916 return;
917
918 RefPtr<Node> oldMarkedNode;
919 if (beforeChange)
920 oldMarkedNode = changeType == ElementInserted ? ElementTraversal::nextSibling(*beforeChange) : beforeChange->nextSibling();
921
922 while (oldMarkedNode && elementAffectsDirectionality(*oldMarkedNode))
923 oldMarkedNode = oldMarkedNode->nextSibling();
924 if (oldMarkedNode)
925 setHasDirAutoFlagRecursively(oldMarkedNode.get(), false);
926
927 for (auto& elementToAdjust : lineageOfType<HTMLElement>(*this)) {
928 if (elementAffectsDirectionality(elementToAdjust)) {
929 elementToAdjust.calculateAndAdjustDirectionality();
930 return;
931 }
932 }
933}
934
935void HTMLElement::addHTMLLengthToStyle(MutableStyleProperties& style, CSSPropertyID propertyID, const String& value)
936{
937 // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
938 // length unit and make the appropriate parsed value.
939
940 if (StringImpl* string = value.impl()) {
941 unsigned parsedLength = 0;
942
943 while (parsedLength < string->length() && (*string)[parsedLength] <= ' ')
944 ++parsedLength;
945
946 for (; parsedLength < string->length(); ++parsedLength) {
947 UChar cc = (*string)[parsedLength];
948 if (cc > '9')
949 break;
950 if (cc < '0') {
951 if (cc == '%' || cc == '*')
952 ++parsedLength;
953 if (cc != '.')
954 break;
955 }
956 }
957
958 if (parsedLength != string->length()) {
959 addPropertyToPresentationAttributeStyle(style, propertyID, string->substring(0, parsedLength));
960 return;
961 }
962 }
963
964 addPropertyToPresentationAttributeStyle(style, propertyID, value);
965}
966
967static RGBA32 parseColorStringWithCrazyLegacyRules(const String& colorString)
968{
969 // Per spec, only look at the first 128 digits of the string.
970 const size_t maxColorLength = 128;
971 // We'll pad the buffer with two extra 0s later, so reserve two more than the max.
972 Vector<char, maxColorLength+2> digitBuffer;
973
974 size_t i = 0;
975 // Skip a leading #.
976 if (colorString[0] == '#')
977 i = 1;
978
979 // Grab the first 128 characters, replacing non-hex characters with 0.
980 // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String.
981 for (; i < colorString.length() && digitBuffer.size() < maxColorLength; i++) {
982 if (!isASCIIHexDigit(colorString[i]))
983 digitBuffer.append('0');
984 else
985 digitBuffer.append(colorString[i]);
986 }
987
988 if (!digitBuffer.size())
989 return Color::black;
990
991 // Pad the buffer out to at least the next multiple of three in size.
992 digitBuffer.append('0');
993 digitBuffer.append('0');
994
995 if (digitBuffer.size() < 6)
996 return makeRGB(toASCIIHexValue(digitBuffer[0]), toASCIIHexValue(digitBuffer[1]), toASCIIHexValue(digitBuffer[2]));
997
998 // Split the digits into three components, then search the last 8 digits of each component.
999 ASSERT(digitBuffer.size() >= 6);
1000 size_t componentLength = digitBuffer.size() / 3;
1001 size_t componentSearchWindowLength = std::min<size_t>(componentLength, 8);
1002 size_t redIndex = componentLength - componentSearchWindowLength;
1003 size_t greenIndex = componentLength * 2 - componentSearchWindowLength;
1004 size_t blueIndex = componentLength * 3 - componentSearchWindowLength;
1005 // Skip digits until one of them is non-zero, or we've only got two digits left in the component.
1006 while (digitBuffer[redIndex] == '0' && digitBuffer[greenIndex] == '0' && digitBuffer[blueIndex] == '0' && (componentLength - redIndex) > 2) {
1007 redIndex++;
1008 greenIndex++;
1009 blueIndex++;
1010 }
1011 ASSERT(redIndex + 1 < componentLength);
1012 ASSERT(greenIndex >= componentLength);
1013 ASSERT(greenIndex + 1 < componentLength * 2);
1014 ASSERT(blueIndex >= componentLength * 2);
1015 ASSERT_WITH_SECURITY_IMPLICATION(blueIndex + 1 < digitBuffer.size());
1016
1017 int redValue = toASCIIHexValue(digitBuffer[redIndex], digitBuffer[redIndex + 1]);
1018 int greenValue = toASCIIHexValue(digitBuffer[greenIndex], digitBuffer[greenIndex + 1]);
1019 int blueValue = toASCIIHexValue(digitBuffer[blueIndex], digitBuffer[blueIndex + 1]);
1020 return makeRGB(redValue, greenValue, blueValue);
1021}
1022
1023// Color parsing that matches HTML's "rules for parsing a legacy color value"
1024void HTMLElement::addHTMLColorToStyle(MutableStyleProperties& style, CSSPropertyID propertyID, const String& attributeValue)
1025{
1026 // An empty string doesn't apply a color. (One containing only whitespace does, which is why this check occurs before stripping.)
1027 if (attributeValue.isEmpty())
1028 return;
1029
1030 String colorString = attributeValue.stripWhiteSpace();
1031
1032 // "transparent" doesn't apply a color either.
1033 if (equalLettersIgnoringASCIICase(colorString, "transparent"))
1034 return;
1035
1036 Color color;
1037 // We can't always use the default Color constructor because it accepts
1038 // 4/8-digit hex, which conflict with some legacy HTML content using attributes.
1039 if ((colorString.length() != 5 && colorString.length() != 9) || colorString[0] != '#')
1040 color = Color(colorString);
1041 if (!color.isValid())
1042 color = Color(parseColorStringWithCrazyLegacyRules(colorString));
1043
1044 style.setProperty(propertyID, CSSValuePool::singleton().createColorValue(color.rgb()));
1045}
1046
1047bool HTMLElement::willRespondToMouseMoveEvents()
1048{
1049 return !isDisabledFormControl() && Element::willRespondToMouseMoveEvents();
1050}
1051
1052bool HTMLElement::willRespondToMouseWheelEvents()
1053{
1054 return !isDisabledFormControl() && Element::willRespondToMouseWheelEvents();
1055}
1056
1057bool HTMLElement::willRespondToMouseClickEvents()
1058{
1059 return !isDisabledFormControl() && Element::willRespondToMouseClickEvents();
1060}
1061
1062bool HTMLElement::canBeActuallyDisabled() const
1063{
1064 return is<HTMLButtonElement>(*this)
1065 || is<HTMLInputElement>(*this)
1066 || is<HTMLSelectElement>(*this)
1067 || is<HTMLTextAreaElement>(*this)
1068 || is<HTMLOptGroupElement>(*this)
1069 || is<HTMLOptionElement>(*this)
1070 || is<HTMLFieldSetElement>(*this);
1071}
1072
1073bool HTMLElement::isActuallyDisabled() const
1074{
1075 return canBeActuallyDisabled() && isDisabledFormControl();
1076}
1077
1078#if ENABLE(IOS_AUTOCORRECT_AND_AUTOCAPITALIZE)
1079
1080const AtomicString& HTMLElement::autocapitalize() const
1081{
1082 return stringForAutocapitalizeType(autocapitalizeType());
1083}
1084
1085AutocapitalizeType HTMLElement::autocapitalizeType() const
1086{
1087 return autocapitalizeTypeForAttributeValue(attributeWithoutSynchronization(HTMLNames::autocapitalizeAttr));
1088}
1089
1090void HTMLElement::setAutocapitalize(const AtomicString& value)
1091{
1092 setAttributeWithoutSynchronization(autocapitalizeAttr, value);
1093}
1094
1095bool HTMLElement::shouldAutocorrect() const
1096{
1097 auto& autocorrectValue = attributeWithoutSynchronization(HTMLNames::autocorrectAttr);
1098 // Unrecognized values fall back to "on".
1099 return !equalLettersIgnoringASCIICase(autocorrectValue, "off");
1100}
1101
1102void HTMLElement::setAutocorrect(bool autocorrect)
1103{
1104 setAttributeWithoutSynchronization(autocorrectAttr, autocorrect ? AtomicString("on", AtomicString::ConstructFromLiteral) : AtomicString("off", AtomicString::ConstructFromLiteral));
1105}
1106
1107#endif
1108
1109InputMode HTMLElement::canonicalInputMode() const
1110{
1111 auto mode = inputModeForAttributeValue(attributeWithoutSynchronization(inputmodeAttr));
1112 if (mode == InputMode::Unspecified) {
1113 if (document().quirks().needsInputModeNoneImplicitly(*this))
1114 return InputMode::None;
1115 }
1116 return mode;
1117}
1118
1119const AtomicString& HTMLElement::inputMode() const
1120{
1121 return stringForInputMode(canonicalInputMode());
1122}
1123
1124void HTMLElement::setInputMode(const AtomicString& value)
1125{
1126 setAttributeWithoutSynchronization(inputmodeAttr, value);
1127}
1128
1129} // namespace WebCore
1130
1131#ifndef NDEBUG
1132
1133// For use in the debugger
1134void dumpInnerHTML(WebCore::HTMLElement*);
1135
1136void dumpInnerHTML(WebCore::HTMLElement* element)
1137{
1138 printf("%s\n", element->innerHTML().ascii().data());
1139}
1140
1141#endif
1142