1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  *           (C) 2003 Dirk Mueller (mueller@kde.org)
7  * Copyright (C) 2002 Apple Computer, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  *
24  */
25 // -------------------------------------------------------------------------
26 //#define DEBUG
27 //#define DEBUG_LAYOUT
28 //#define PAR_DEBUG
29 //#define EVENT_DEBUG
30 //#define UNSUPPORTED_ATTR
31 
32 #include "html_elementimpl.h"
33 #include "dtd.h"
34 #include "html_documentimpl.h"
35 #include "htmltokenizer.h"
36 
37 #include <khtmlview.h>
38 #include <khtml_part.h>
39 
40 #include <rendering/render_object.h>
41 #include <rendering/render_replaced.h>
42 #include <css/css_valueimpl.h>
43 #include <css/css_stylesheetimpl.h>
44 #include <css/cssproperties.h>
45 #include <css/cssvalues.h>
46 #include <xml/dom_textimpl.h>
47 #include <xml/dom2_eventsimpl.h>
48 #include <dom/dom_doc.h>
49 
50 #include "khtml_debug.h"
51 
52 #undef FOCUS_EVENT  // for win32, MinGW
53 
54 using namespace DOM;
55 using namespace khtml;
56 
HTMLElementImpl(DocumentImpl * doc)57 HTMLElementImpl::HTMLElementImpl(DocumentImpl *doc)
58     : ElementImpl(doc)
59 {
60     m_htmlCompat = doc && doc->htmlMode() != DocumentImpl::XHtml;
61 }
62 
~HTMLElementImpl()63 HTMLElementImpl::~HTMLElementImpl()
64 {
65 }
66 
isInline() const67 bool HTMLElementImpl::isInline() const
68 {
69     if (renderer()) {
70         return ElementImpl::isInline();
71     }
72 
73     switch (id()) {
74     case ID_A:
75     case ID_FONT:
76     case ID_TT:
77     case ID_U:
78     case ID_B:
79     case ID_I:
80     case ID_S:
81     case ID_STRIKE:
82     case ID_BIG:
83     case ID_SMALL:
84 
85     // %phrase
86     case ID_EM:
87     case ID_STRONG:
88     case ID_DFN:
89     case ID_CODE:
90     case ID_SAMP:
91     case ID_KBD:
92     case ID_VAR:
93     case ID_CITE:
94     case ID_ABBR:
95     case ID_ACRONYM:
96 
97     // %special
98     case ID_SUB:
99     case ID_SUP:
100     case ID_SPAN:
101     case ID_NOBR:
102     case ID_WBR:
103         return true;
104 
105     default:
106         return ElementImpl::isInline();
107     }
108 }
109 
namespaceURI() const110 DOMString HTMLElementImpl::namespaceURI() const
111 {
112     return DOMString(XHTML_NAMESPACE);
113 }
114 
parseAttribute(AttributeImpl * attr)115 void HTMLElementImpl::parseAttribute(AttributeImpl *attr)
116 {
117     DOMString indexstring;
118     switch (attr->id()) {
119     case ATTR_ALIGN:
120         if (attr->val()) {
121             if (strcasecmp(attr->value(), "middle") == 0) {
122                 addCSSProperty(CSS_PROP_TEXT_ALIGN, CSS_VAL_CENTER);
123             } else {
124                 addCSSProperty(CSS_PROP_TEXT_ALIGN, attr->value().lower());
125             }
126         } else {
127             removeCSSProperty(CSS_PROP_TEXT_ALIGN);
128         }
129         break;
130 // the core attributes...
131     case ATTR_ID:
132         // unique id
133         setHasID();
134         document()->incDOMTreeVersion(DocumentImpl::TV_IDNameHref);
135         break;
136     case ATTR_CLASS:
137         if (attr->val()) {
138             DOMString v = attr->value();
139             const QChar *characters = v.unicode();
140             unsigned length = v.length();
141             unsigned i;
142             for (i = 0; i < length && characters[i].isSpace(); ++i) { }
143             setHasClass(i < length);
144             attributes()->setClass(v);
145         } else {
146             setHasClass(false);
147         }
148         document()->incDOMTreeVersion(DocumentImpl::TV_Class);
149         break;
150     case ATTR_NAME:
151         document()->incDOMTreeVersion(DocumentImpl::TV_IDNameHref);
152         break;
153     case ATTR_CONTENTEDITABLE:
154         setContentEditable(attr);
155         break;
156     case ATTR_STYLE:
157         getInlineStyleDecls()->updateFromAttribute(attr->value());
158         setChanged();
159         break;
160     case ATTR_TABINDEX:
161         indexstring = getAttribute(ATTR_TABINDEX);
162         if (attr->val()) {
163             setTabIndex(attr->value().toInt());
164         } else {
165             setNoTabIndex();
166         }
167         break;
168 // i18n attributes
169     case ATTR_LANG:
170         break;
171     case ATTR_DIR:
172         addCSSProperty(CSS_PROP_DIRECTION, attr->value().lower());
173         break;
174 // standard events
175     case ATTR_ONCLICK:
176         setHTMLEventListener(EventImpl::KHTML_ECMA_CLICK_EVENT,
177                              document()->createHTMLEventListener(attr->value().string(), "onclick", this));
178         break;
179     case ATTR_ONDBLCLICK:
180         setHTMLEventListener(EventImpl::KHTML_ECMA_DBLCLICK_EVENT,
181                              document()->createHTMLEventListener(attr->value().string(), "ondblclick", this));
182         break;
183     case ATTR_ONMOUSEDOWN:
184         setHTMLEventListener(EventImpl::MOUSEDOWN_EVENT,
185                              document()->createHTMLEventListener(attr->value().string(), "onmousedown", this));
186         break;
187     case ATTR_ONMOUSEMOVE:
188         setHTMLEventListener(EventImpl::MOUSEMOVE_EVENT,
189                              document()->createHTMLEventListener(attr->value().string(), "onmousemove", this));
190         break;
191     case ATTR_ONMOUSEOUT:
192         setHTMLEventListener(EventImpl::MOUSEOUT_EVENT,
193                              document()->createHTMLEventListener(attr->value().string(), "onmouseout", this));
194         break;
195     case ATTR_ONMOUSEOVER:
196         setHTMLEventListener(EventImpl::MOUSEOVER_EVENT,
197                              document()->createHTMLEventListener(attr->value().string(), "onmouseover", this));
198         break;
199     case ATTR_ONMOUSEUP:
200         setHTMLEventListener(EventImpl::MOUSEUP_EVENT,
201                              document()->createHTMLEventListener(attr->value().string(), "onmouseup", this));
202         break;
203     case ATTR_ONKEYDOWN:
204         setHTMLEventListener(EventImpl::KEYDOWN_EVENT,
205                              document()->createHTMLEventListener(attr->value().string(), "onkeydown", this));
206         break;
207     case ATTR_ONKEYPRESS:
208         setHTMLEventListener(EventImpl::KEYPRESS_EVENT,
209                              document()->createHTMLEventListener(attr->value().string(), "onkeypress", this));
210         break;
211     case ATTR_ONKEYUP:
212         setHTMLEventListener(EventImpl::KEYUP_EVENT,
213                              document()->createHTMLEventListener(attr->value().string(), "onkeyup", this));
214         break;
215     case ATTR_ONFOCUS:
216         setHTMLEventListener(EventImpl::FOCUS_EVENT,
217                              document()->createHTMLEventListener(attr->value().string(), "onfocus", this));
218         break;
219     case ATTR_ONBLUR:
220         setHTMLEventListener(EventImpl::BLUR_EVENT,
221                              document()->createHTMLEventListener(attr->value().string(), "onblur", this));
222         break;
223     case ATTR_ONSCROLL:
224         setHTMLEventListener(EventImpl::SCROLL_EVENT,
225                              document()->createHTMLEventListener(attr->value().string(), "onscroll", this));
226         break;
227 // other misc attributes
228     default:
229 #ifdef UNSUPPORTED_ATTR
230         qCDebug(KHTML_LOG) << "UATTR: <" << this->nodeName().string() << "> ["
231                  << attr->name().string() << "]=[" << attr->value().string() << "]";
232 #endif
233         break;
234     }
235 }
236 
recalcStyle(StyleChange ch)237 void HTMLElementImpl::recalcStyle(StyleChange ch)
238 {
239     ElementImpl::recalcStyle(ch);
240 
241     if (m_render /*&& changed*/) {
242         m_render->updateFromElement();
243     }
244 }
245 
addCSSProperty(int id,const DOMString & value)246 void HTMLElementImpl::addCSSProperty(int id, const DOMString &value)
247 {
248     if (!m_hasCombinedStyle) {
249         createNonCSSDecl();
250     }
251     nonCSSStyleDecls()->setProperty(id, value, false);
252     setChanged();
253 }
254 
addCSSProperty(int id,int value)255 void HTMLElementImpl::addCSSProperty(int id, int value)
256 {
257     if (!m_hasCombinedStyle) {
258         createNonCSSDecl();
259     }
260     nonCSSStyleDecls()->setProperty(id, value, false);
261     setChanged();
262 }
263 
addCSSLength(int id,const DOMString & value,bool numOnly,bool multiLength)264 void HTMLElementImpl::addCSSLength(int id, const DOMString &value, bool numOnly, bool multiLength)
265 {
266     if (!m_hasCombinedStyle) {
267         createNonCSSDecl();
268     }
269 
270     // strip attribute garbage to avoid CSS parsing errors
271     // ### create specialized hook that avoids parsing every
272     // value twice!
273     if (value.implementation()) {
274         // match \s*[+-]?\d*(\.\d*)?[%\*]?
275         unsigned i = 0, j = 0;
276         QChar *s = value.implementation()->s;
277         unsigned l = value.implementation()->l;
278 
279         while (i < l && s[i].isSpace()) {
280             ++i;
281         }
282         if (i < l && (s[i] == '+' || s[i] == '-')) {
283             ++i;
284         }
285         while (i < l && s[i].isDigit()) {
286             ++i, ++j;
287         }
288 
289         // no digits!
290         if (j == 0) {
291             return;
292         }
293 
294         int v = qBound(-8192, QString::fromRawData(s, i).toInt(), 8191);
295         const char *suffix = "px";
296         if (!numOnly || multiLength) {
297             // look if we find a % or *
298             while (i < l) {
299                 if (multiLength && s[i] == '*') {
300                     suffix = "";
301                     break;
302                 }
303                 if (s[i] == '%') {
304                     suffix = "%";
305                     break;
306                 }
307                 ++i;
308             }
309         }
310         if (numOnly) {
311             suffix = "";
312         }
313 
314         QString ns = QString::number(v) + suffix;
315         nonCSSStyleDecls()->setLengthProperty(id, DOMString(ns), false, multiLength);
316         setChanged();
317         return;
318     }
319 
320     nonCSSStyleDecls()->setLengthProperty(id, value, false, multiLength);
321     setChanged();
322 }
323 
isHexDigit(const QChar & c)324 static inline bool isHexDigit(const QChar &c)
325 {
326     return (c >= '0' && c <= '9') ||
327            (c >= 'a' && c <= 'f') ||
328            (c >= 'A' && c <= 'F');
329 }
330 
toHex(const QChar & c)331 static inline int toHex(const QChar &c)
332 {
333     return ((c >= '0' && c <= '9')
334             ? (c.unicode() - '0')
335             : ((c >= 'a' && c <= 'f')
336                ? (c.unicode() - 'a' + 10)
337                : ((c >= 'A' && c <= 'F')
338                   ? (c.unicode() - 'A' + 10)
339                   : -1)));
340 }
341 
342 /* color parsing that tries to match as close as possible IE 6. */
addHTMLColor(int id,const DOMString & c)343 void HTMLElementImpl::addHTMLColor(int id, const DOMString &c)
344 {
345     if (!m_hasCombinedStyle) {
346         createNonCSSDecl();
347     }
348 
349     // this is the only case no color gets applied in IE.
350     if (!c.length()) {
351         removeCSSProperty(id);
352         return;
353     }
354 
355     if (nonCSSStyleDecls()->setProperty(id, c, false)) {
356         return;
357     }
358 
359     QString color = c.string();
360     // not something that fits the specs.
361 
362     // we're emulating IEs color parser here. It maps transparent to black, otherwise it tries to build a rgb value
363     // out of everyhting you put in. The algorithm is experimentally determined, but seems to work for all test cases I have.
364 
365     // the length of the color value is rounded up to the next
366     // multiple of 3. each part of the rgb triple then gets one third
367     // of the length.
368     //
369     // Each triplet is parsed byte by byte, mapping
370     // each number to a hex value (0-9a-fA-F to their values
371     // everything else to 0).
372     //
373     // The highest non zero digit in all triplets is remembered, and
374     // used as a normalization point to normalize to values between 0
375     // and 255.
376 
377     if (color.toLower() != "transparent") {
378         if (color[0] == '#') {
379             color.remove(0,  1);
380         }
381         int basicLength = (color.length() + 2) / 3;
382         if (basicLength > 1) {
383             // IE ignores colors with three digits or less
384 //      qDebug("trying to fix up color '%s'. basicLength=%d, length=%d",
385 //         color.toLatin1().constData(), basicLength, color.length() );
386             int colors[3] = { 0, 0, 0 };
387             int component = 0;
388             int pos = 0;
389             int maxDigit = basicLength - 1;
390             while (component < 3) {
391                 // search forward for digits in the string
392                 int numDigits = 0;
393                 while (pos < (int)color.length() && numDigits < basicLength) {
394                     int hex = toHex(color[pos]);
395                     colors[component] = (colors[component] << 4);
396                     if (hex > 0) {
397                         colors[component] += hex;
398                         maxDigit = qMin(maxDigit, numDigits);
399                     }
400                     numDigits++;
401                     pos++;
402                 }
403                 while (numDigits++ < basicLength) {
404                     colors[component] <<= 4;
405                 }
406                 component++;
407             }
408             maxDigit = basicLength - maxDigit;
409 //      qDebug("color is %x %x %x, maxDigit=%d",  colors[0], colors[1], colors[2], maxDigit );
410 
411             // normalize to 00-ff. The highest filled digit counts, minimum is 2 digits
412             maxDigit -= 2;
413             colors[0] >>= 4 * maxDigit;
414             colors[1] >>= 4 * maxDigit;
415             colors[2] >>= 4 * maxDigit;
416 //      qDebug("normalized color is %x %x %x",  colors[0], colors[1], colors[2] );
417             //  assert( colors[0] < 0x100 && colors[1] < 0x100 && colors[2] < 0x100 );
418 
419             color.sprintf("#%02x%02x%02x", colors[0], colors[1], colors[2]);
420 //      qDebug( "trying to add fixed color string '%s'", color.toLatin1().constData() );
421             if (nonCSSStyleDecls()->setProperty(id, DOMString(color), false)) {
422                 return;
423             }
424         }
425     }
426     nonCSSStyleDecls()->setProperty(id, CSS_VAL_BLACK, false);
427 }
428 
removeCSSProperty(int id)429 void HTMLElementImpl::removeCSSProperty(int id)
430 {
431     if (!m_hasCombinedStyle) {
432         return;
433     }
434     nonCSSStyleDecls()->setParent(document()->elementSheet());
435     nonCSSStyleDecls()->removeProperty(id);
436     setChanged();
437 }
438 
innerHTML() const439 DOMString HTMLElementImpl::innerHTML() const
440 {
441     QString result = ""; //Use QString to accumulate since DOMString is poor for appends
442     for (NodeImpl *child = firstChild(); child != nullptr; child = child->nextSibling()) {
443         DOMString kid = child->toString();
444         result += QString::fromRawData(kid.unicode(), kid.length());
445     }
446     return result;
447 }
448 
innerText() const449 DOMString HTMLElementImpl::innerText() const
450 {
451     QString text = "";
452     if (!firstChild()) {
453         return text;
454     }
455 
456     const NodeImpl *n = this;
457     // find the next text/image after the anchor, to get a position
458     while (n) {
459         if (n->firstChild()) {
460             n = n->firstChild();
461         } else if (n->nextSibling()) {
462             n = n->nextSibling();
463         } else {
464             NodeImpl *next = nullptr;
465             while (!next) {
466                 n = n->parentNode();
467                 if (!n || n == (NodeImpl *)this) {
468                     goto end;
469                 }
470                 next = n->nextSibling();
471             }
472             n = next;
473         }
474         if (n->isTextNode()) {
475             DOMStringImpl *data = static_cast<const TextImpl *>(n)->string();
476             text += QString::fromRawData(data->s, data->l);
477         }
478     }
479 end:
480     return text;
481 }
482 
createContextualFragment(const DOMString & html)483 DocumentFragment HTMLElementImpl::createContextualFragment(const DOMString &html)
484 {
485     // IE originally restricted innerHTML to a small subset of elements;
486     // and we largely matched that. Mozilla's embrace of innerHTML, however, extended
487     // it to pretty much everything, and so the web (and HTML5) requires it now.
488     // For now, we accept everything, but do not do context-based recovery in the parser.
489     if (!document()->isHTMLDocument()) {
490         return DocumentFragment();
491     }
492 
493     DocumentFragmentImpl *fragment = new DocumentFragmentImpl(docPtr());
494     DocumentFragment f(fragment);
495     {
496         HTMLTokenizer tok(docPtr(), fragment);
497         tok.begin();
498         tok.write(html.string(), true);
499         tok.end();
500     }
501 
502     // Exceptions are ignored because none ought to happen here.
503     int ignoredExceptionCode;
504 
505     // we need to pop <html> and <body> elements and remove <head> to
506     // accomadate folks passing complete HTML documents to make the
507     // child of an element.
508     for (NodeImpl *node = fragment->firstChild(); node;) {
509         if (node->id() == ID_HTML || node->id() == ID_BODY) {
510             NodeImpl *firstChild = node->firstChild();
511             NodeImpl *child = firstChild;
512             while (child) {
513                 NodeImpl *nextChild = child->nextSibling();
514                 fragment->insertBefore(child, node, ignoredExceptionCode);
515                 child = nextChild;
516             }
517             if (!firstChild) {
518                 NodeImpl *nextNode = node->nextSibling();
519                 fragment->removeChild(node, ignoredExceptionCode);
520                 node = nextNode;
521             } else {
522                 fragment->removeChild(node, ignoredExceptionCode);
523                 node = firstChild;
524             }
525         } else if (node->id() == ID_HEAD) {
526             NodeImpl *nextNode = node->nextSibling();
527             fragment->removeChild(node, ignoredExceptionCode);
528             node = nextNode;
529         } else {
530             node = node->nextSibling();
531         }
532     }
533 
534     return f;
535 }
536 
setInnerHTML(const DOMString & html,int & exceptioncode)537 void HTMLElementImpl::setInnerHTML(const DOMString &html, int &exceptioncode)
538 {
539     if (id() == ID_SCRIPT || id() == ID_STYLE) {
540         // Script and CSS source shouldn't be parsed as HTML.
541         removeChildren();
542         appendChild(document()->createTextNode(html), exceptioncode);
543         return;
544     }
545 
546     DocumentFragment fragment = createContextualFragment(html);
547     if (fragment.isNull()) {
548         exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR;
549         return;
550     }
551 
552     // Make sure adding the new child is ok, before removing all children (#96187)
553     checkAddChild(fragment.handle(), exceptioncode);
554     if (exceptioncode) {
555         return;
556     }
557 
558     removeChildren();
559     appendChild(fragment.handle(), exceptioncode);
560 }
561 
setInnerText(const DOMString & text,int & exceptioncode)562 void HTMLElementImpl::setInnerText(const DOMString &text, int &exceptioncode)
563 {
564     // following the IE specs.
565     if (endTagRequirement(id()) == FORBIDDEN) {
566         exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR;
567         return;
568     }
569     // IE disallows innerHTML on inline elements. I don't see why we should have this restriction, as our
570     // dhtml engine can cope with it. Lars
571     //if ( isInline() ) return false;
572     switch (id()) {
573     case ID_COL:
574     case ID_COLGROUP:
575     case ID_FRAMESET:
576     case ID_HEAD:
577     case ID_HTML:
578     case ID_TABLE:
579     case ID_TBODY:
580     case ID_TFOOT:
581     case ID_THEAD:
582     case ID_TR:
583         exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR;
584         return;
585     default:
586         break;
587     }
588 
589     removeChildren();
590 
591     TextImpl *t = new TextImpl(docPtr(), text.implementation());
592     appendChild(t, exceptioncode);
593 }
594 
addHTMLAlignment(DOMString alignment)595 void HTMLElementImpl::addHTMLAlignment(DOMString alignment)
596 {
597     //qDebug("alignment is %s", alignment.string().toLatin1().constData() );
598     // vertical alignment with respect to the current baseline of the text
599     // right or left means floating images
600     int propfloat = -1;
601     int propvalign = -1;
602     if (strcasecmp(alignment, "absmiddle") == 0) {
603         propvalign = CSS_VAL_MIDDLE;
604     } else if (strcasecmp(alignment, "absbottom") == 0) {
605         propvalign = CSS_VAL_BOTTOM;
606     } else if (strcasecmp(alignment, "left") == 0) {
607         propfloat = CSS_VAL_LEFT;
608         propvalign = CSS_VAL_TOP;
609     } else if (strcasecmp(alignment, "right") == 0) {
610         propfloat = CSS_VAL_RIGHT;
611         propvalign = CSS_VAL_TOP;
612     } else if (strcasecmp(alignment, "top") == 0) {
613         propvalign = CSS_VAL_TOP;
614     } else if (strcasecmp(alignment, "middle") == 0) {
615         propvalign = CSS_VAL__KHTML_BASELINE_MIDDLE;
616     } else if (strcasecmp(alignment, "center") == 0) {
617         propvalign = CSS_VAL_MIDDLE;
618     } else if (strcasecmp(alignment, "bottom") == 0) {
619         propvalign = CSS_VAL_BASELINE;
620     } else if (strcasecmp(alignment, "texttop") == 0) {
621         propvalign = CSS_VAL_TEXT_TOP;
622     }
623 
624     if (propfloat != -1) {
625         addCSSProperty(CSS_PROP_FLOAT, propfloat);
626     }
627     if (propvalign != -1) {
628         addCSSProperty(CSS_PROP_VERTICAL_ALIGN, propvalign);
629     }
630 }
631 
contentEditable() const632 DOMString HTMLElementImpl::contentEditable() const
633 {
634     document()->updateRendering();
635 
636     if (!renderer()) {
637         return "false";
638     }
639 
640     switch (renderer()->style()->userInput()) {
641     case UI_ENABLED:
642         return "true";
643     case UI_DISABLED:
644     case UI_NONE:
645         return "false";
646     default:;
647     }
648     return "inherit";
649 }
650 
setContentEditable(AttributeImpl * attr)651 void HTMLElementImpl::setContentEditable(AttributeImpl *attr)
652 {
653     const DOMString &enabled = attr->value();
654     if (enabled.isEmpty() || strcasecmp(enabled, "true") == 0) {
655         addCSSProperty(CSS_PROP__KHTML_USER_INPUT, CSS_VAL_ENABLED);
656     } else if (strcasecmp(enabled, "false") == 0) {
657         addCSSProperty(CSS_PROP__KHTML_USER_INPUT, CSS_VAL_NONE);
658     } else if (strcasecmp(enabled, "inherit") == 0) {
659         addCSSProperty(CSS_PROP__KHTML_USER_INPUT, CSS_VAL_INHERIT);
660     }
661 }
662 
setContentEditable(const DOMString & enabled)663 void HTMLElementImpl::setContentEditable(const DOMString &enabled)
664 {
665     if (enabled == "inherit") {
666         int exceptionCode;
667         removeAttribute(ATTR_CONTENTEDITABLE, exceptionCode);
668     } else {
669         setAttribute(ATTR_CONTENTEDITABLE, enabled.isEmpty() ? "true" : enabled);
670     }
671 }
672 
toString() const673 DOMString HTMLElementImpl::toString() const
674 {
675     if (!hasChildNodes()) {
676         DOMString result = openTagStartToString();
677         result += ">";
678 
679         if (endTagRequirement(id()) == REQUIRED) {
680             result += "</";
681             result += nonCaseFoldedTagName();
682             result += ">";
683         }
684 
685         return result;
686     }
687 
688     return ElementImpl::toString();
689 }
690 
691 // -------------------------------------------------------------------------
HTMLGenericElementImpl(DocumentImpl * doc,ushort i)692 HTMLGenericElementImpl::HTMLGenericElementImpl(DocumentImpl *doc, ushort i)
693     : HTMLElementImpl(doc)
694 {
695     m_localName = LocalName::fromId(i);
696 }
697 
HTMLGenericElementImpl(DocumentImpl * doc,LocalName l)698 HTMLGenericElementImpl::HTMLGenericElementImpl(DocumentImpl *doc, LocalName l)
699     : HTMLElementImpl(doc),
700       m_localName(l)
701 {}
702 
~HTMLGenericElementImpl()703 HTMLGenericElementImpl::~HTMLGenericElementImpl()
704 {
705 }
706