2010-12-07 Maciej Stachowiak <mjs@apple.com>
[WebKit.git] / WebCore / html / HTMLElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@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  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23
24 #include "config.h"
25 #include "HTMLElement.h"
26
27 #include "Attribute.h"
28 #include "CSSPropertyNames.h"
29 #include "CSSValueKeywords.h"
30 #include "DocumentFragment.h"
31 #include "Event.h"
32 #include "EventListener.h"
33 #include "EventNames.h"
34 #include "ExceptionCode.h"
35 #include "Frame.h"
36 #include "HTMLBRElement.h"
37 #include "HTMLCollection.h"
38 #include "HTMLDocument.h"
39 #include "HTMLElementFactory.h"
40 #include "HTMLFormElement.h"
41 #include "HTMLNames.h"
42 #include "HTMLParserIdioms.h"
43 #include "RenderWordBreak.h"
44 #include "ScriptEventListener.h"
45 #include "Settings.h"
46 #include "Text.h"
47 #include "TextIterator.h"
48 #include "markup.h"
49 #include <wtf/StdLibExtras.h>
50 #include <wtf/text/CString.h>
51
52 namespace WebCore {
53
54 using namespace HTMLNames;
55
56 using std::min;
57 using std::max;
58
59 PassRefPtr<HTMLElement> HTMLElement::create(const QualifiedName& tagName, Document* document)
60 {
61     return adoptRef(new HTMLElement(tagName, document));
62 }
63
64 String HTMLElement::nodeName() const
65 {
66     // FIXME: Would be nice to have an atomicstring lookup based off uppercase
67     // chars that does not have to copy the string on a hit in the hash.
68     // FIXME: We should have a way to detect XHTML elements and replace the hasPrefix() check with it.
69     if (document()->isHTMLDocument() && !tagQName().hasPrefix())
70         return tagQName().localNameUpper();
71     return Element::nodeName();
72 }
73
74 bool HTMLElement::ieForbidsInsertHTML() const
75 {
76     // FIXME: Supposedly IE disallows settting innerHTML, outerHTML
77     // and createContextualFragment on these tags.  We have no tests to
78     // verify this however, so this list could be totally wrong.
79     // This list was moved from the previous endTagRequirement() implementation.
80     // This is also called from editing and assumed to be the list of tags
81     // for which no end tag should be serialized. It's unclear if the list for
82     // IE compat and the list for serialization sanity are the same.
83     if (hasLocalName(areaTag)
84         || hasLocalName(baseTag)
85         || hasLocalName(basefontTag)
86         || hasLocalName(brTag)
87         || hasLocalName(colTag)
88 #if ENABLE(DATAGRID)
89         || hasLocalName(dcellTag)
90         || hasLocalName(dcolTag)
91 #endif
92         || hasLocalName(embedTag)
93         || hasLocalName(frameTag)
94         || hasLocalName(hrTag)
95         || hasLocalName(imageTag)
96         || hasLocalName(imgTag)
97         || hasLocalName(inputTag)
98         || hasLocalName(isindexTag)
99         || hasLocalName(linkTag)
100         || hasLocalName(metaTag)
101         || hasLocalName(paramTag)
102         || hasLocalName(sourceTag)
103         || hasLocalName(wbrTag))
104         return true;
105     // FIXME: I'm not sure why dashboard mode would want to change the
106     // serialization of <canvas>, that seems like a bad idea.
107 #if ENABLE(DASHBOARD_SUPPORT)
108     if (hasLocalName(canvasTag)) {
109         Settings* settings = document()->settings();
110         if (settings && settings->usesDashboardBackwardCompatibilityMode())
111             return true;
112     }
113 #endif
114     return false;
115 }
116
117 bool HTMLElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
118 {
119     if (attrName == alignAttr
120         || attrName == contenteditableAttr
121         || attrName == hiddenAttr) {
122         result = eUniversal;
123         return false;
124     }
125     if (attrName == dirAttr) {
126         result = hasLocalName(bdoTag) ? eBDO : eUniversal;
127         return false;
128     }
129
130     return StyledElement::mapToEntry(attrName, result);
131 }
132     
133 void HTMLElement::parseMappedAttribute(Attribute* attr)
134 {
135     if (isIdAttributeName(attr->name()) || attr->name() == classAttr || attr->name() == styleAttr)
136         return StyledElement::parseMappedAttribute(attr);
137
138     String indexstring;
139     if (attr->name() == alignAttr) {
140         if (equalIgnoringCase(attr->value(), "middle"))
141             addCSSProperty(attr, CSSPropertyTextAlign, "center");
142         else
143             addCSSProperty(attr, CSSPropertyTextAlign, attr->value());
144     } else if (attr->name() == contenteditableAttr) {
145         setContentEditable(attr);
146     } else if (attr->name() == hiddenAttr) {
147         addCSSProperty(attr, CSSPropertyDisplay, CSSValueNone);
148     } else if (attr->name() == tabindexAttr) {
149         indexstring = getAttribute(tabindexAttr);
150         int tabindex = 0;
151         if (parseHTMLInteger(indexstring, tabindex)) {
152             // Clamp tabindex to the range of 'short' to match Firefox's behavior.
153             setTabIndexExplicitly(max(static_cast<int>(std::numeric_limits<short>::min()), min(tabindex, static_cast<int>(std::numeric_limits<short>::max()))));
154         }
155     } else if (attr->name() == langAttr) {
156         // FIXME: Implement
157     } else if (attr->name() == dirAttr) {
158         addCSSProperty(attr, CSSPropertyDirection, attr->value());
159         addCSSProperty(attr, CSSPropertyUnicodeBidi, hasLocalName(bdoTag) ? CSSValueBidiOverride : CSSValueEmbed);
160     } else if (attr->name() == draggableAttr) {
161         const AtomicString& value = attr->value();
162         if (equalIgnoringCase(value, "true")) {
163             addCSSProperty(attr, CSSPropertyWebkitUserDrag, CSSValueElement);
164             addCSSProperty(attr, CSSPropertyWebkitUserSelect, CSSValueNone);
165         } else if (equalIgnoringCase(value, "false"))
166             addCSSProperty(attr, CSSPropertyWebkitUserDrag, CSSValueNone);
167     }
168 // standard events
169     else if (attr->name() == onclickAttr) {
170         setAttributeEventListener(eventNames().clickEvent, createAttributeEventListener(this, attr));
171     } else if (attr->name() == oncontextmenuAttr) {
172         setAttributeEventListener(eventNames().contextmenuEvent, createAttributeEventListener(this, attr));
173     } else if (attr->name() == ondblclickAttr) {
174         setAttributeEventListener(eventNames().dblclickEvent, createAttributeEventListener(this, attr));
175     } else if (attr->name() == onmousedownAttr) {
176         setAttributeEventListener(eventNames().mousedownEvent, createAttributeEventListener(this, attr));
177     } else if (attr->name() == onmousemoveAttr) {
178         setAttributeEventListener(eventNames().mousemoveEvent, createAttributeEventListener(this, attr));
179     } else if (attr->name() == onmouseoutAttr) {
180         setAttributeEventListener(eventNames().mouseoutEvent, createAttributeEventListener(this, attr));
181     } else if (attr->name() == onmouseoverAttr) {
182         setAttributeEventListener(eventNames().mouseoverEvent, createAttributeEventListener(this, attr));
183     } else if (attr->name() == onmouseupAttr) {
184         setAttributeEventListener(eventNames().mouseupEvent, createAttributeEventListener(this, attr));
185     } else if (attr->name() == onmousewheelAttr) {
186         setAttributeEventListener(eventNames().mousewheelEvent, createAttributeEventListener(this, attr));
187     } else if (attr->name() == onfocusAttr) {
188         setAttributeEventListener(eventNames().focusEvent, createAttributeEventListener(this, attr));
189     } else if (attr->name() == onfocusinAttr) {
190         setAttributeEventListener(eventNames().focusinEvent, createAttributeEventListener(this, attr));
191     } else if (attr->name() == onfocusoutAttr) {
192         setAttributeEventListener(eventNames().focusoutEvent, createAttributeEventListener(this, attr));
193     } else if (attr->name() == onblurAttr) {
194         setAttributeEventListener(eventNames().blurEvent, createAttributeEventListener(this, attr));
195     } else if (attr->name() == onkeydownAttr) {
196         setAttributeEventListener(eventNames().keydownEvent, createAttributeEventListener(this, attr));
197     } else if (attr->name() == onkeypressAttr) {
198         setAttributeEventListener(eventNames().keypressEvent, createAttributeEventListener(this, attr));
199     } else if (attr->name() == onkeyupAttr) {
200         setAttributeEventListener(eventNames().keyupEvent, createAttributeEventListener(this, attr));
201     } else if (attr->name() == onscrollAttr) {
202         setAttributeEventListener(eventNames().scrollEvent, createAttributeEventListener(this, attr));
203     } else if (attr->name() == onbeforecutAttr) {
204         setAttributeEventListener(eventNames().beforecutEvent, createAttributeEventListener(this, attr));
205     } else if (attr->name() == oncutAttr) {
206         setAttributeEventListener(eventNames().cutEvent, createAttributeEventListener(this, attr));
207     } else if (attr->name() == onbeforecopyAttr) {
208         setAttributeEventListener(eventNames().beforecopyEvent, createAttributeEventListener(this, attr));
209     } else if (attr->name() == oncopyAttr) {
210         setAttributeEventListener(eventNames().copyEvent, createAttributeEventListener(this, attr));
211     } else if (attr->name() == onbeforepasteAttr) {
212         setAttributeEventListener(eventNames().beforepasteEvent, createAttributeEventListener(this, attr));
213     } else if (attr->name() == onpasteAttr) {
214         setAttributeEventListener(eventNames().pasteEvent, createAttributeEventListener(this, attr));
215     } else if (attr->name() == ondragenterAttr) {
216         setAttributeEventListener(eventNames().dragenterEvent, createAttributeEventListener(this, attr));
217     } else if (attr->name() == ondragoverAttr) {
218         setAttributeEventListener(eventNames().dragoverEvent, createAttributeEventListener(this, attr));
219     } else if (attr->name() == ondragleaveAttr) {
220         setAttributeEventListener(eventNames().dragleaveEvent, createAttributeEventListener(this, attr));
221     } else if (attr->name() == ondropAttr) {
222         setAttributeEventListener(eventNames().dropEvent, createAttributeEventListener(this, attr));
223     } else if (attr->name() == ondragstartAttr) {
224         setAttributeEventListener(eventNames().dragstartEvent, createAttributeEventListener(this, attr));
225     } else if (attr->name() == ondragAttr) {
226         setAttributeEventListener(eventNames().dragEvent, createAttributeEventListener(this, attr));
227     } else if (attr->name() == ondragendAttr) {
228         setAttributeEventListener(eventNames().dragendEvent, createAttributeEventListener(this, attr));
229     } else if (attr->name() == onselectstartAttr) {
230         setAttributeEventListener(eventNames().selectstartEvent, createAttributeEventListener(this, attr));
231     } else if (attr->name() == onsubmitAttr) {
232         setAttributeEventListener(eventNames().submitEvent, createAttributeEventListener(this, attr));
233     } else if (attr->name() == onerrorAttr) {
234         setAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(this, attr));
235     } else if (attr->name() == onwebkitanimationstartAttr) {
236         setAttributeEventListener(eventNames().webkitAnimationStartEvent, createAttributeEventListener(this, attr));
237     } else if (attr->name() == onwebkitanimationiterationAttr) {
238         setAttributeEventListener(eventNames().webkitAnimationIterationEvent, createAttributeEventListener(this, attr));
239     } else if (attr->name() == onwebkitanimationendAttr) {
240         setAttributeEventListener(eventNames().webkitAnimationEndEvent, createAttributeEventListener(this, attr));
241     } else if (attr->name() == onwebkittransitionendAttr) {
242         setAttributeEventListener(eventNames().webkitTransitionEndEvent, createAttributeEventListener(this, attr));
243     } else if (attr->name() == oninputAttr) {
244         setAttributeEventListener(eventNames().inputEvent, createAttributeEventListener(this, attr));
245     } else if (attr->name() == oninvalidAttr) {
246         setAttributeEventListener(eventNames().invalidEvent, createAttributeEventListener(this, attr));
247     } else if (attr->name() == ontouchstartAttr) {
248         setAttributeEventListener(eventNames().touchstartEvent, createAttributeEventListener(this, attr));
249     } else if (attr->name() == ontouchmoveAttr) {
250         setAttributeEventListener(eventNames().touchmoveEvent, createAttributeEventListener(this, attr));
251     } else if (attr->name() == ontouchendAttr) {
252         setAttributeEventListener(eventNames().touchendEvent, createAttributeEventListener(this, attr));
253     } else if (attr->name() == ontouchcancelAttr) {
254         setAttributeEventListener(eventNames().touchcancelEvent, createAttributeEventListener(this, attr));
255 #if ENABLE(FULLSCREEN_API)
256     } else if (attr->name() == onwebkitfullscreenchangeAttr) {
257         setAttributeEventListener(eventNames().webkitfullscreenchangeEvent, createAttributeEventListener(this, attr));
258 #endif
259     }
260 }
261
262 String HTMLElement::innerHTML() const
263 {
264     return createMarkup(this, ChildrenOnly);
265 }
266
267 String HTMLElement::outerHTML() const
268 {
269     return createMarkup(this);
270 }
271
272 // FIXME: This logic should move into Range::createContextualFragment
273 PassRefPtr<DocumentFragment> HTMLElement::deprecatedCreateContextualFragment(const String& markup, FragmentScriptingPermission scriptingPermission)
274 {
275     // The following is in accordance with the definition as used by IE.
276     if (ieForbidsInsertHTML())
277         return 0;
278
279     if (hasLocalName(colTag) || hasLocalName(colgroupTag) || hasLocalName(framesetTag)
280         || hasLocalName(headTag) || hasLocalName(styleTag) || hasLocalName(titleTag))
281         return 0;
282
283     return Element::deprecatedCreateContextualFragment(markup, scriptingPermission);
284 }
285
286 static inline bool hasOneChild(ContainerNode* node)
287 {
288     Node* firstChild = node->firstChild();
289     return firstChild && !firstChild->nextSibling();
290 }
291
292 static inline bool hasOneTextChild(ContainerNode* node)
293 {
294     return hasOneChild(node) && node->firstChild()->isTextNode();
295 }
296
297 static void replaceChildrenWithFragment(HTMLElement* element, PassRefPtr<DocumentFragment> fragment, ExceptionCode& ec)
298 {
299     if (!fragment->firstChild()) {
300         element->removeChildren();
301         return;
302     }
303
304     if (hasOneTextChild(element) && hasOneTextChild(fragment.get())) {
305         static_cast<Text*>(element->firstChild())->setData(static_cast<Text*>(fragment->firstChild())->data(), ec);
306         return;
307     }
308
309     if (hasOneChild(element)) {
310         element->replaceChild(fragment, element->firstChild(), ec);
311         return;
312     }
313
314     element->removeChildren();
315     element->appendChild(fragment, ec);
316 }
317
318 static void replaceChildrenWithText(HTMLElement* element, const String& text, ExceptionCode& ec)
319 {
320     if (hasOneTextChild(element)) {
321         static_cast<Text*>(element->firstChild())->setData(text, ec);
322         return;
323     }
324
325     RefPtr<Text> textNode = Text::create(element->document(), text);
326
327     if (hasOneChild(element)) {
328         element->replaceChild(textNode.release(), element->firstChild(), ec);
329         return;
330     }
331
332     element->removeChildren();
333     element->appendChild(textNode.release(), ec);
334 }
335
336 // We may want to move a version of this function into DocumentFragment.h/cpp
337 static PassRefPtr<DocumentFragment> createFragmentFromSource(const String& markup, Element* contextElement, ExceptionCode& ec)
338 {
339     Document* document = contextElement->document();
340     RefPtr<DocumentFragment> fragment;
341
342     fragment = DocumentFragment::create(document);
343     if (document->isHTMLDocument()) {
344         fragment->parseHTML(markup, contextElement);
345         return fragment;
346     }
347
348     bool wasValid = fragment->parseXML(markup, contextElement);
349     if (!wasValid) {
350         ec = INVALID_STATE_ERR;
351         return 0;
352     }
353     return fragment;
354 }
355
356 void HTMLElement::setInnerHTML(const String& html, ExceptionCode& ec)
357 {
358     RefPtr<DocumentFragment> fragment = createFragmentFromSource(html, this, ec);
359     if (fragment)
360         replaceChildrenWithFragment(this, fragment.release(), ec);
361 }
362
363 void HTMLElement::setOuterHTML(const String& html, ExceptionCode& ec)
364 {
365     Node* p = parentNode();
366     if (!p || !p->isHTMLElement()) {
367         ec = NO_MODIFICATION_ALLOWED_ERR;
368         return;
369     }
370     HTMLElement* parent = static_cast<HTMLElement*>(p);
371
372     RefPtr<DocumentFragment> fragment = createFragmentFromSource(html, parent, ec);
373     if (fragment) {
374         // FIXME: Why doesn't this have code to merge neighboring text nodes the way setOuterText does?
375         parent->replaceChild(fragment.release(), this, ec);
376     }
377 }
378
379 void HTMLElement::setInnerText(const String& text, ExceptionCode& ec)
380 {
381     if (ieForbidsInsertHTML()) {
382         ec = NO_MODIFICATION_ALLOWED_ERR;
383         return;
384     }
385     if (hasLocalName(colTag) || hasLocalName(colgroupTag) || hasLocalName(framesetTag) ||
386         hasLocalName(headTag) || hasLocalName(htmlTag) || hasLocalName(tableTag) || 
387         hasLocalName(tbodyTag) || hasLocalName(tfootTag) || hasLocalName(theadTag) ||
388         hasLocalName(trTag)) {
389         ec = NO_MODIFICATION_ALLOWED_ERR;
390         return;
391     }
392
393     // FIXME: This doesn't take whitespace collapsing into account at all.
394
395     if (!text.contains('\n') && !text.contains('\r')) {
396         if (text.isEmpty()) {
397             removeChildren();
398             return;
399         }
400         replaceChildrenWithText(this, text, ec);
401         return;
402     }
403
404     // FIXME: Do we need to be able to detect preserveNewline style even when there's no renderer?
405     // FIXME: Can the renderer be out of date here? Do we need to call updateStyleIfNeeded?
406     // For example, for the contents of textarea elements that are display:none?
407     RenderObject* r = renderer();
408     if (r && r->style()->preserveNewline()) {
409         if (!text.contains('\r')) {
410             replaceChildrenWithText(this, text, ec);
411             return;
412         }
413         String textWithConsistentLineBreaks = text;
414         textWithConsistentLineBreaks.replace("\r\n", "\n");
415         textWithConsistentLineBreaks.replace('\r', '\n');
416         replaceChildrenWithText(this, textWithConsistentLineBreaks, ec);
417         return;
418     }
419
420     // Add text nodes and <br> elements.
421     ec = 0;
422     RefPtr<DocumentFragment> fragment = DocumentFragment::create(document());
423     int lineStart = 0;
424     UChar prev = 0;
425     int length = text.length();
426     for (int i = 0; i < length; ++i) {
427         UChar c = text[i];
428         if (c == '\n' || c == '\r') {
429             if (i > lineStart) {
430                 fragment->appendChild(Text::create(document(), text.substring(lineStart, i - lineStart)), ec);
431                 if (ec)
432                     return;
433             }
434             if (!(c == '\n' && i != 0 && prev == '\r')) {
435                 fragment->appendChild(HTMLBRElement::create(document()), ec);
436                 if (ec)
437                     return;
438             }
439             lineStart = i + 1;
440         }
441         prev = c;
442     }
443     if (length > lineStart)
444         fragment->appendChild(Text::create(document(), text.substring(lineStart, length - lineStart)), ec);
445     replaceChildrenWithFragment(this, fragment.release(), ec);
446 }
447
448 void HTMLElement::setOuterText(const String &text, ExceptionCode& ec)
449 {
450     if (ieForbidsInsertHTML()) {
451         ec = NO_MODIFICATION_ALLOWED_ERR;
452         return;
453     }
454     if (hasLocalName(colTag) || hasLocalName(colgroupTag) || hasLocalName(framesetTag) ||
455         hasLocalName(headTag) || hasLocalName(htmlTag) || hasLocalName(tableTag) || 
456         hasLocalName(tbodyTag) || hasLocalName(tfootTag) || hasLocalName(theadTag) ||
457         hasLocalName(trTag)) {
458         ec = NO_MODIFICATION_ALLOWED_ERR;
459         return;
460     }
461
462     ContainerNode* parent = parentNode();
463     if (!parent) {
464         ec = NO_MODIFICATION_ALLOWED_ERR;
465         return;
466     }
467
468     // FIXME: This creates a new text node even when the text is empty.
469     // FIXME: This creates a single text node even when the text has CR and LF
470     // characters in it. Instead it should create <br> elements.
471     RefPtr<Text> t = Text::create(document(), text);
472     ec = 0;
473     parent->replaceChild(t, this, ec);
474     if (ec)
475         return;
476
477     // Is previous node a text node? If so, merge into it.
478     Node* prev = t->previousSibling();
479     if (prev && prev->isTextNode()) {
480         Text* textPrev = static_cast<Text*>(prev);
481         textPrev->appendData(t->data(), ec);
482         if (ec)
483             return;
484         t->remove(ec);
485         if (ec)
486             return;
487         t = textPrev;
488     }
489
490     // Is next node a text node? If so, merge it in.
491     Node* next = t->nextSibling();
492     if (next && next->isTextNode()) {
493         Text* textNext = static_cast<Text*>(next);
494         t->appendData(textNext->data(), ec);
495         if (ec)
496             return;
497         textNext->remove(ec);
498         if (ec)
499             return;
500     }
501 }
502
503 Node* HTMLElement::insertAdjacent(const String& where, Node* newChild, ExceptionCode& ec)
504 {
505     // In Internet Explorer if the element has no parent and where is "beforeBegin" or "afterEnd",
506     // a document fragment is created and the elements appended in the correct order. This document
507     // fragment isn't returned anywhere.
508     //
509     // This is impossible for us to implement as the DOM tree does not allow for such structures,
510     // Opera also appears to disallow such usage.
511
512     if (equalIgnoringCase(where, "beforeBegin")) {
513         ContainerNode* parent = this->parentNode();
514         return (parent && parent->insertBefore(newChild, this, ec)) ? newChild : 0;
515     }
516
517     if (equalIgnoringCase(where, "afterBegin"))
518         return insertBefore(newChild, firstChild(), ec) ? newChild : 0;
519
520     if (equalIgnoringCase(where, "beforeEnd"))
521         return appendChild(newChild, ec) ? newChild : 0;
522
523     if (equalIgnoringCase(where, "afterEnd")) {
524         ContainerNode* parent = this->parentNode();
525         return (parent && parent->insertBefore(newChild, nextSibling(), ec)) ? newChild : 0;
526     }
527     
528     // IE throws COM Exception E_INVALIDARG; this is the best DOM exception alternative.
529     ec = NOT_SUPPORTED_ERR;
530     return 0;
531 }
532
533 Element* HTMLElement::insertAdjacentElement(const String& where, Element* newChild, ExceptionCode& ec)
534 {
535     if (!newChild) {
536         // IE throws COM Exception E_INVALIDARG; this is the best DOM exception alternative.
537         ec = TYPE_MISMATCH_ERR;
538         return 0;
539     }
540
541     Node* returnValue = insertAdjacent(where, newChild, ec);
542     ASSERT(!returnValue || returnValue->isElementNode());
543     return static_cast<Element*>(returnValue); 
544 }
545
546 // Step 3 of http://www.whatwg.org/specs/web-apps/current-work/multipage/apis-in-html-documents.html#insertadjacenthtml()
547 static Element* contextElementForInsertion(const String& where, Element* element, ExceptionCode& ec)
548 {
549     if (equalIgnoringCase(where, "beforeBegin") || equalIgnoringCase(where, "afterEnd")) {
550         ContainerNode* parent = element->parentNode();
551         if (parent && parent->isDocumentNode()) {
552             ec = NO_MODIFICATION_ALLOWED_ERR;
553             return 0;
554         }
555         ASSERT(!parent || parent->isElementNode());
556         return static_cast<Element*>(parent);
557     }
558     if (equalIgnoringCase(where, "afterBegin") || equalIgnoringCase(where, "beforeEnd"))
559         return element;
560     ec =  SYNTAX_ERR;
561     return 0;
562 }
563
564 void HTMLElement::insertAdjacentHTML(const String& where, const String& markup, ExceptionCode& ec)
565 {
566     RefPtr<DocumentFragment> fragment = document()->createDocumentFragment();
567     Element* contextElement = contextElementForInsertion(where, this, ec);
568     if (!contextElement)
569         return;
570
571     if (document()->isHTMLDocument())
572          fragment->parseHTML(markup, contextElement);
573     else {
574         if (!fragment->parseXML(markup, contextElement))
575             // FIXME: We should propagate a syntax error exception out here.
576             return;
577     }
578
579     insertAdjacent(where, fragment.get(), ec);
580 }
581
582 void HTMLElement::insertAdjacentText(const String& where, const String& text, ExceptionCode& ec)
583 {
584     RefPtr<Text> textNode = document()->createTextNode(text);
585     insertAdjacent(where, textNode.get(), ec);
586 }
587
588 void HTMLElement::addHTMLAlignment(Attribute* attr)
589 {
590     addHTMLAlignmentToStyledElement(this, attr);
591 }
592
593 void HTMLElement::addHTMLAlignmentToStyledElement(StyledElement* element, Attribute* attr)
594 {
595     // Vertical alignment with respect to the current baseline of the text
596     // right or left means floating images.
597     int floatValue = CSSValueInvalid;
598     int verticalAlignValue = CSSValueInvalid;
599
600     const AtomicString& alignment = attr->value();
601     if (equalIgnoringCase(alignment, "absmiddle"))
602         verticalAlignValue = CSSValueMiddle;
603     else if (equalIgnoringCase(alignment, "absbottom"))
604         verticalAlignValue = CSSValueBottom;
605     else if (equalIgnoringCase(alignment, "left")) {
606         floatValue = CSSValueLeft;
607         verticalAlignValue = CSSValueTop;
608     } else if (equalIgnoringCase(alignment, "right")) {
609         floatValue = CSSValueRight;
610         verticalAlignValue = CSSValueTop;
611     } else if (equalIgnoringCase(alignment, "top"))
612         verticalAlignValue = CSSValueTop;
613     else if (equalIgnoringCase(alignment, "middle"))
614         verticalAlignValue = CSSValueWebkitBaselineMiddle;
615     else if (equalIgnoringCase(alignment, "center"))
616         verticalAlignValue = CSSValueMiddle;
617     else if (equalIgnoringCase(alignment, "bottom"))
618         verticalAlignValue = CSSValueBaseline;
619     else if (equalIgnoringCase(alignment, "texttop"))
620         verticalAlignValue = CSSValueTextTop;
621
622     if (floatValue != CSSValueInvalid)
623         element->addCSSProperty(attr, CSSPropertyFloat, floatValue);
624
625     if (verticalAlignValue != CSSValueInvalid)
626         element->addCSSProperty(attr, CSSPropertyVerticalAlign, verticalAlignValue);
627 }
628
629 bool HTMLElement::supportsFocus() const
630 {
631     return Element::supportsFocus() || (isContentEditable() && parentNode() && !parentNode()->isContentEditable());
632 }
633
634 bool HTMLElement::isContentEditable() const 
635 {
636     if (document()->frame() && document()->frame()->isContentEditable())
637         return true;
638
639     // Ideally we'd call ASSERT!needsStyleRecalc()) here, but
640     // ContainerNode::setFocus() calls setNeedsStyleRecalc(), so the assertion
641     // would fire in the middle of Document::setFocusedNode().
642
643     if (!renderer()) {
644         if (parentNode())
645             return parentNode()->isContentEditable();
646         else
647             return false;
648     }
649     
650     return renderer()->style()->userModify() == READ_WRITE || renderer()->style()->userModify() == READ_WRITE_PLAINTEXT_ONLY;
651 }
652
653 bool HTMLElement::isContentRichlyEditable() const
654 {
655     if (document()->frame() && document()->frame()->isContentEditable())
656         return true;
657
658     if (!renderer()) {
659         if (parentNode())
660             return parentNode()->isContentEditable();
661         else
662             return false;
663     }
664     
665     return renderer()->style()->userModify() == READ_WRITE;
666 }
667
668 String HTMLElement::contentEditable() const 
669 {
670     if (!renderer())
671         return "false";
672     
673     switch (renderer()->style()->userModify()) {
674         case READ_WRITE:
675             return "true";
676         case READ_ONLY:
677             return "false";
678         case READ_WRITE_PLAINTEXT_ONLY:
679             return "plaintext-only";
680         default:
681             return "inherit";
682     }
683 }
684
685 void HTMLElement::setContentEditable(Attribute* attr) 
686 {
687     const AtomicString& enabled = attr->value();
688     if (enabled.isEmpty() || equalIgnoringCase(enabled, "true")) {
689         addCSSProperty(attr, CSSPropertyWebkitUserModify, CSSValueReadWrite);
690         addCSSProperty(attr, CSSPropertyWordWrap, CSSValueBreakWord);
691         addCSSProperty(attr, CSSPropertyWebkitNbspMode, CSSValueSpace);
692         addCSSProperty(attr, CSSPropertyWebkitLineBreak, CSSValueAfterWhiteSpace);
693     } else if (equalIgnoringCase(enabled, "false")) {
694         addCSSProperty(attr, CSSPropertyWebkitUserModify, CSSValueReadOnly);
695         attr->decl()->removeProperty(CSSPropertyWordWrap, false);
696         attr->decl()->removeProperty(CSSPropertyWebkitNbspMode, false);
697         attr->decl()->removeProperty(CSSPropertyWebkitLineBreak, false);
698     } else if (equalIgnoringCase(enabled, "inherit")) {
699         addCSSProperty(attr, CSSPropertyWebkitUserModify, CSSValueInherit);
700         attr->decl()->removeProperty(CSSPropertyWordWrap, false);
701         attr->decl()->removeProperty(CSSPropertyWebkitNbspMode, false);
702         attr->decl()->removeProperty(CSSPropertyWebkitLineBreak, false);
703     } else if (equalIgnoringCase(enabled, "plaintext-only")) {
704         addCSSProperty(attr, CSSPropertyWebkitUserModify, CSSValueReadWritePlaintextOnly);
705         addCSSProperty(attr, CSSPropertyWordWrap, CSSValueBreakWord);
706         addCSSProperty(attr, CSSPropertyWebkitNbspMode, CSSValueSpace);
707         addCSSProperty(attr, CSSPropertyWebkitLineBreak, CSSValueAfterWhiteSpace);
708     }
709 }
710
711 void HTMLElement::setContentEditable(const String &enabled)
712 {
713     if (enabled == "inherit") {
714         ExceptionCode ec;
715         removeAttribute(contenteditableAttr, ec);
716     }
717     else
718         setAttribute(contenteditableAttr, enabled.isEmpty() ? "true" : enabled);
719 }
720
721 bool HTMLElement::draggable() const
722 {
723     return equalIgnoringCase(getAttribute(draggableAttr), "true");
724 }
725
726 void HTMLElement::setDraggable(bool value)
727 {
728     setAttribute(draggableAttr, value ? "true" : "false");
729 }
730
731 bool HTMLElement::spellcheck() const
732 {
733     return isSpellCheckingEnabled();
734 }
735
736 void HTMLElement::setSpellcheck(bool enable)
737 {
738     setAttribute(spellcheckAttr, enable ? "true" : "false");
739 }
740
741
742 void HTMLElement::click()
743 {
744     dispatchSimulatedClick(0, false, false);
745 }
746
747 // accessKeyAction is used by the accessibility support code
748 // to send events to elements that our JavaScript caller does
749 // does not.  The elements JS is interested in have subclasses
750 // that override this method to direct the click appropriately.
751 // Here in the base class, then, we only send the click if
752 // the caller wants it to go to any HTMLElement, and we say
753 // to send the mouse events in addition to the click.
754 void HTMLElement::accessKeyAction(bool sendToAnyElement)
755 {
756     if (sendToAnyElement)
757         dispatchSimulatedClick(0, true);
758 }
759
760 String HTMLElement::title() const
761 {
762     return getAttribute(titleAttr);
763 }
764
765 short HTMLElement::tabIndex() const
766 {
767     if (supportsFocus())
768         return Element::tabIndex();
769     return -1;
770 }
771
772 void HTMLElement::setTabIndex(int value)
773 {
774     setAttribute(tabindexAttr, String::number(value));
775 }
776
777 PassRefPtr<HTMLCollection> HTMLElement::children()
778 {
779     return HTMLCollection::create(this, NodeChildren);
780 }
781
782 bool HTMLElement::rendererIsNeeded(RenderStyle *style)
783 {
784 #if !ENABLE(XHTMLMP)
785     if (hasLocalName(noscriptTag)) {
786         Frame* frame = document()->frame();
787         if (frame && frame->script()->canExecuteScripts(NotAboutToExecuteScript))
788             return false;
789     } else
790 #endif
791     if (hasLocalName(noembedTag)) {
792         Frame* frame = document()->frame();
793         if (frame && frame->loader()->subframeLoader()->allowPlugins(NotAboutToInstantiatePlugin))
794             return false;
795     }
796     return StyledElement::rendererIsNeeded(style);
797 }
798
799 RenderObject* HTMLElement::createRenderer(RenderArena* arena, RenderStyle* style)
800 {
801     if (hasLocalName(wbrTag))
802         return new (arena) RenderWordBreak(this);
803     return RenderObject::createObject(this, style);
804 }
805
806 HTMLFormElement* HTMLElement::findFormAncestor() const
807 {
808     for (ContainerNode* ancestor = parentNode(); ancestor; ancestor = ancestor->parentNode()) {
809         if (ancestor->hasTagName(formTag))
810             return static_cast<HTMLFormElement*>(ancestor);
811     }
812     return 0;
813 }
814
815 HTMLFormElement* HTMLElement::virtualForm() const
816 {
817     return findFormAncestor();
818 }
819
820 } // namespace WebCore
821
822 #ifndef NDEBUG
823
824 // For use in the debugger
825 void dumpInnerHTML(WebCore::HTMLElement*);
826
827 void dumpInnerHTML(WebCore::HTMLElement* element)
828 {
829     printf("%s\n", element->innerHTML().ascii().data());
830 }
831 #endif