Reviewed by Eric.
Fixes: https://bugs.webkit.org/show_bug.cgi?id=20372

Refactor HTMLScriptElement's code into a common base class: ScriptElement.
SVGScriptElement will be converted to use ScriptElement in a follow-up patch.

This resolves code duplications and allows us to completly replace the old
SVGScriptElement (which doesn't use CachedScript, no dynamic injected scripts etc..)

As ScriptElement, doesn't actually inherit from Element, we may want to rename
it, though StyleElement uses the same naming convention, so I left it as is for now.
Eventually we'll rename both files in future.

No functional changes yet, as SVGScriptElement doesn't yet use the new base class.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@35744 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/WebCore/html/HTMLScriptElement.cpp b/WebCore/html/HTMLScriptElement.cpp
index a084354..5e7df34 100644
--- a/WebCore/html/HTMLScriptElement.cpp
+++ b/WebCore/html/HTMLScriptElement.cpp
@@ -23,15 +23,9 @@
 #include "config.h"
 #include "HTMLScriptElement.h"
 
-#include "CachedScript.h"
-#include "DocLoader.h"
 #include "Document.h"
 #include "EventNames.h"
-#include "Frame.h"
-#include "FrameLoader.h"
 #include "HTMLNames.h"
-#include "ScriptController.h"
-#include "MIMETypeRegistry.h"
 #include "Text.h"
 
 namespace WebCore {
@@ -41,54 +35,42 @@
 
 HTMLScriptElement::HTMLScriptElement(Document* doc)
     : HTMLElement(scriptTag, doc)
-    , m_cachedScript(0)
-    , m_createdByParser(false)
-    , m_evaluated(false)
+    , m_data(this, this)
 {
 }
 
 HTMLScriptElement::~HTMLScriptElement()
 {
-    if (m_cachedScript)
-        m_cachedScript->removeClient(this);
 }
 
 bool HTMLScriptElement::isURLAttribute(Attribute* attr) const
 {
-    return attr->name() == srcAttr;
+    return attr->name() == sourceAttributeValue();
+}
+
+void HTMLScriptElement::setCreatedByParser(bool createdByParser)
+{
+    m_data.setCreatedByParser(createdByParser);
+}
+
+bool HTMLScriptElement::shouldExecuteAsJavaScript() const
+{
+    return m_data.shouldExecuteAsJavaScript();
 }
 
 void HTMLScriptElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
 {
-    // If a node is inserted as a child of the script element
-    // and the script element has been inserted in the document
-    // we evaluate the script.
-    if (!m_createdByParser && inDocument() && firstChild())
-        evaluateScript(document()->url().string(), text());
+    ScriptElement::childrenChanged(m_data);
     HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
 }
 
 void HTMLScriptElement::parseMappedAttribute(MappedAttribute* attr)
 {
     const QualifiedName& attrName = attr->name();
-    if (attrName == srcAttr) {
-        if (m_evaluated || m_cachedScript || m_createdByParser || !inDocument())
-            return;
 
-        // FIXME: Evaluate scripts in viewless documents.
-        // See http://bugs.webkit.org/show_bug.cgi?id=5727
-        if (!document()->frame())
-            return;
-    
-        const AtomicString& url = attr->value();
-        if (!url.isEmpty()) {
-            m_cachedScript = document()->docLoader()->requestScript(url, scriptCharset());
-            if (m_cachedScript)
-                m_cachedScript->addClient(this);
-            else
-                dispatchHTMLEvent(errorEvent, true, false);
-        }
-    } else if (attrName == onloadAttr)
+    if (attrName == srcAttr)
+        handleSourceAttribute(m_data, attr->value());
+    else if (attrName == onloadAttr)
         setHTMLEventListener(loadEvent, attr);
     else
         HTMLElement::parseMappedAttribute(attr);
@@ -96,187 +78,40 @@
 
 void HTMLScriptElement::finishParsingChildren()
 {
-    // The parser just reached </script>. If we have no src and no text,
-    // allow dynamic loading later.
-    if (getAttribute(srcAttr).isEmpty() && text().isEmpty())
-        setCreatedByParser(false);
+    ScriptElement::finishParsingChildren(m_data, sourceAttributeValue());
     HTMLElement::finishParsingChildren();
 }
 
 void HTMLScriptElement::insertedIntoDocument()
 {
     HTMLElement::insertedIntoDocument();
-
-    ASSERT(!m_cachedScript);
-
-    if (m_createdByParser)
-        return;
-    
-    // FIXME: Eventually we'd like to evaluate scripts which are inserted into a 
-    // viewless document but this'll do for now.
-    // See http://bugs.webkit.org/show_bug.cgi?id=5727
-    if (!document()->frame())
-        return;
-    
-    const AtomicString& url = getAttribute(srcAttr);
-    if (!url.isEmpty()) {
-        m_cachedScript = document()->docLoader()->requestScript(url, scriptCharset());
-        if (m_cachedScript)
-            m_cachedScript->addClient(this);
-        else
-            dispatchHTMLEvent(errorEvent, true, false);
-        return;
-    }
-
-    // If there's an empty script node, we shouldn't evaluate the script
-    // because if a script is inserted afterwards (by setting text or innerText)
-    // it should be evaluated, and evaluateScript only evaluates a script once.
-    String scriptString = text();    
-    if (!scriptString.isEmpty())
-        evaluateScript(document()->url().string(), scriptString);
+    ScriptElement::insertedIntoDocument(m_data, sourceAttributeValue());
 }
 
 void HTMLScriptElement::removedFromDocument()
 {
     HTMLElement::removedFromDocument();
-
-    if (m_cachedScript) {
-        m_cachedScript->removeClient(this);
-        m_cachedScript = 0;
-    }
-}
-
-void HTMLScriptElement::notifyFinished(CachedResource* o)
-{
-    CachedScript* cs = static_cast<CachedScript*>(o);
-
-    ASSERT(cs == m_cachedScript);
-
-    // Evaluating the script could lead to a garbage collection which
-    // can delete the script element so we need to protect it.
-    RefPtr<HTMLScriptElement> protect(this);
-    
-    if (cs->errorOccurred())
-        dispatchHTMLEvent(errorEvent, true, false);
-    else {
-        evaluateScript(cs->url(), cs->script());
-        dispatchHTMLEvent(loadEvent, false, false);
-    }
-
-    // script evaluation may have dereffed it already
-    if (m_cachedScript) {
-        m_cachedScript->removeClient(this);
-        m_cachedScript = 0;
-    }
-}
-
-bool HTMLScriptElement::shouldExecuteAsJavaScript()
-{
-    /*
-         Mozilla 1.8 accepts javascript1.0 - javascript1.7, but WinIE 7 accepts only javascript1.1 - javascript1.3.
-         Mozilla 1.8 and WinIE 7 both accept javascript and livescript.
-         WinIE 7 accepts ecmascript and jscript, but Mozilla 1.8 doesn't.
-         Neither Mozilla 1.8 nor WinIE 7 accept leading or trailing whitespace.
-         We want to accept all the values that either of these browsers accept, but not other values.
-     */
-    static const AtomicString validLanguages[] = {
-        "javascript",
-        "javascript1.0",
-        "javascript1.1",
-        "javascript1.2",
-        "javascript1.3",
-        "javascript1.4",
-        "javascript1.5",
-        "javascript1.6",
-        "javascript1.7",
-        "livescript",
-        "ecmascript",
-        "jscript"
-    };
-    static const unsigned validLanguagesCount = sizeof(validLanguages) / sizeof(validLanguages[0]); 
-
-    const AtomicString& type = getAttribute(typeAttr);
-    if (!type.isEmpty()) {
-        String lowerType = type.string().stripWhiteSpace().lower();
-        if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(lowerType))
-            return true;
-
-        return false;
-    }
-
-    const AtomicString& language = getAttribute(languageAttr);
-    if (!language.isEmpty()) {
-        String lowerLanguage = language.string().lower();
-        for (unsigned i = 0; i < validLanguagesCount; ++i)
-            if (lowerLanguage == validLanguages[i])
-                return true;
-
-        return false;
-    }
-
-    // No type or language is specified, so we assume the script to be JavaScript
-    return true;
-}
-
-void HTMLScriptElement::evaluateScript(const String& url, const String& script)
-{
-    if (m_evaluated)
-        return;
-    
-    if (!shouldExecuteAsJavaScript())
-        return;
-    
-    Frame* frame = document()->frame();
-    if (frame) {
-        if (frame->script()->isEnabled()) {
-            m_evaluated = true;
-            // FIXME: This starting line number will be incorrect for evaluation triggered
-            // from insertedIntoDocument or childrenChanged.
-            frame->script()->evaluate(url, 1, script);
-            Document::updateDocumentsRendering();
-        }
-    }
+    ScriptElement::removedFromDocument(m_data);
 }
 
 String HTMLScriptElement::text() const
 {
-    Vector<UChar> val;
-    Text* firstTextNode = 0;
-    bool foundMultipleTextNodes = false;
-    
-    for (Node* n = firstChild(); n; n = n->nextSibling()) {
-        if (n->isTextNode()) {
-            if (foundMultipleTextNodes)
-                append(val, static_cast<Text*>(n)->data());
-            else if (firstTextNode) {
-                append(val, firstTextNode->data());
-                append(val, static_cast<Text*>(n)->data());
-                foundMultipleTextNodes = true;
-            } else {
-                firstTextNode = static_cast<Text*>(n);
-            }
-        }
-    }
-        
-    if (firstTextNode && !foundMultipleTextNodes)
-        return firstTextNode->data();
-    
-    return String::adopt(val);
+    return m_data.scriptContent();
 }
 
 void HTMLScriptElement::setText(const String &value)
 {
     ExceptionCode ec = 0;
     int numChildren = childNodeCount();
-    
+
     if (numChildren == 1 && firstChild()->isTextNode()) {
-        static_cast<Text *>(firstChild())->setData(value, ec);
+        static_cast<Text*>(firstChild())->setData(value, ec);
         return;
     }
-    
+
     if (numChildren > 0)
         removeChildren();
-    
+
     appendChild(document()->createTextNode(value.impl()), ec);
 }
 
@@ -286,7 +121,7 @@
     return String();
 }
 
-void HTMLScriptElement::setHtmlFor(const String& /*value*/)
+void HTMLScriptElement::setHtmlFor(const String&)
 {
     // DOM Level 1 says: reserved for future use.
 }
@@ -297,14 +132,14 @@
     return String();
 }
 
-void HTMLScriptElement::setEvent(const String& /*value*/)
+void HTMLScriptElement::setEvent(const String&)
 {
     // DOM Level 1 says: reserved for future use.
 }
 
 String HTMLScriptElement::charset() const
 {
-    return getAttribute(charsetAttr);
+    return charsetAttributeValue();
 }
 
 void HTMLScriptElement::setCharset(const String &value)
@@ -324,7 +159,7 @@
 
 KURL HTMLScriptElement::src() const
 {
-    return document()->completeURL(getAttribute(srcAttr));
+    return document()->completeURL(sourceAttributeValue());
 }
 
 void HTMLScriptElement::setSrc(const String &value)
@@ -334,7 +169,7 @@
 
 String HTMLScriptElement::type() const
 {
-    return getAttribute(typeAttr);
+    return typeAttributeValue();
 }
 
 void HTMLScriptElement::setType(const String &value)
@@ -344,15 +179,12 @@
 
 String HTMLScriptElement::scriptCharset() const
 {
-    // First we try to get encoding from charset attribute.
-    String charset = getAttribute(charsetAttr).string().stripWhiteSpace();
-    // If charset has not been declared in script tag, fall back
-    // to frame encoding.
-    if (charset.isEmpty()) {
-        if (Frame* frame = document()->frame())
-            charset = frame->loader()->encoding();
-    }
-    return charset;
+    return m_data.scriptCharset();
+}
+
+String HTMLScriptElement::scriptContent() const
+{
+    return m_data.scriptContent();
 }
 
 void HTMLScriptElement::getSubresourceAttributeStrings(Vector<String>& urls) const
@@ -360,4 +192,34 @@
     urls.append(src().string());
 }
 
+String HTMLScriptElement::sourceAttributeValue() const
+{
+    return getAttribute(srcAttr).string();
+}
+
+String HTMLScriptElement::charsetAttributeValue() const
+{
+    return getAttribute(charsetAttr).string();;
+}
+
+String HTMLScriptElement::typeAttributeValue() const
+{
+    return getAttribute(typeAttr).string();;
+}
+
+String HTMLScriptElement::languageAttributeValue() const
+{
+    return getAttribute(languageAttr).string();
+}
+
+void HTMLScriptElement::dispatchLoadEvent()
+{
+    dispatchHTMLEvent(loadEvent, false, false);
+}
+
+void HTMLScriptElement::dispatchErrorEvent()
+{
+    dispatchHTMLEvent(errorEvent, true, false);
+}
+
 }