WebCore/dom/ScriptElement.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 /*
       
     2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
       
     3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
       
     4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
       
     5  * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
       
     6  * Copyright (C) 2008 Nikolas Zimmermann <zimmermann@kde.org>
       
     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 #include "config.h"
       
    25 #include "ScriptElement.h"
       
    26 
       
    27 #include "CachedScript.h"
       
    28 #include "DocLoader.h"
       
    29 #include "Document.h"
       
    30 #include "Frame.h"
       
    31 #include "FrameLoader.h"
       
    32 #include "HTMLNames.h"
       
    33 #include "HTMLScriptElement.h"
       
    34 #include "MIMETypeRegistry.h"
       
    35 #include "Page.h"
       
    36 #include "ScriptController.h"
       
    37 #include "ScriptSourceCode.h"
       
    38 #include "ScriptValue.h"
       
    39 #include "Settings.h"
       
    40 #include "StringHash.h"
       
    41 #include "Text.h"
       
    42 #include <wtf/StdLibExtras.h>
       
    43 
       
    44 #if ENABLE(SVG)
       
    45 #include "SVGNames.h"
       
    46 #include "SVGScriptElement.h"
       
    47 #endif
       
    48 
       
    49 namespace WebCore {
       
    50 
       
    51 void ScriptElement::insertedIntoDocument(ScriptElementData& data, const String& sourceUrl)
       
    52 {
       
    53     if (data.createdByParser())
       
    54         return;
       
    55 
       
    56     if (!sourceUrl.isEmpty()) {
       
    57         data.requestScript(sourceUrl);
       
    58         return;
       
    59     }
       
    60 
       
    61     // If there's an empty script node, we shouldn't evaluate the script
       
    62     // because if a script is inserted afterwards (by setting text or innerText)
       
    63     // it should be evaluated, and evaluateScript only evaluates a script once.
       
    64     data.evaluateScript(ScriptSourceCode(data.scriptContent(), data.element()->document()->url())); // FIXME: Provide a real starting line number here.
       
    65 }
       
    66 
       
    67 void ScriptElement::removedFromDocument(ScriptElementData& data)
       
    68 {
       
    69     // Eventually stop loading any not-yet-finished content
       
    70     data.stopLoadRequest();
       
    71 }
       
    72 
       
    73 void ScriptElement::childrenChanged(ScriptElementData& data)
       
    74 {
       
    75     if (data.createdByParser())
       
    76         return;
       
    77 
       
    78     Element* element = data.element();
       
    79 
       
    80     // If a node is inserted as a child of the script element
       
    81     // and the script element has been inserted in the document
       
    82     // we evaluate the script.
       
    83     if (element->inDocument() && element->firstChild())
       
    84         data.evaluateScript(ScriptSourceCode(data.scriptContent(), element->document()->url())); // FIXME: Provide a real starting line number here
       
    85 }
       
    86 
       
    87 static inline bool useHTML5Parser(Document* document)
       
    88 {
       
    89     ASSERT(document);
       
    90     Settings* settings = document->page() ? document->page()->settings() : 0;
       
    91     return settings && settings->html5ParserEnabled();
       
    92 }
       
    93 
       
    94 void ScriptElement::finishParsingChildren(ScriptElementData& data, const String& sourceUrl)
       
    95 {
       
    96     // The parser just reached </script>. If we have no src and no text,
       
    97     // allow dynamic loading later.
       
    98     if (sourceUrl.isEmpty() && data.scriptContent().isEmpty())
       
    99         data.setCreatedByParser(false);
       
   100 }
       
   101 
       
   102 void ScriptElement::handleSourceAttribute(ScriptElementData& data, const String& sourceUrl)
       
   103 {
       
   104     if (data.ignoresLoadRequest() || sourceUrl.isEmpty())
       
   105         return;
       
   106 
       
   107     data.requestScript(sourceUrl);
       
   108 }
       
   109 
       
   110 // Helper function
       
   111 static bool isSupportedJavaScriptLanguage(const String& language)
       
   112 {
       
   113     typedef HashSet<String, CaseFoldingHash> LanguageSet;
       
   114     DEFINE_STATIC_LOCAL(LanguageSet, languages, ());
       
   115     if (languages.isEmpty()) {
       
   116         languages.add("javascript");
       
   117         languages.add("javascript");
       
   118         languages.add("javascript1.0");
       
   119         languages.add("javascript1.1");
       
   120         languages.add("javascript1.2");
       
   121         languages.add("javascript1.3");
       
   122         languages.add("javascript1.4");
       
   123         languages.add("javascript1.5");
       
   124         languages.add("javascript1.6");
       
   125         languages.add("javascript1.7");
       
   126         languages.add("livescript");
       
   127         languages.add("ecmascript");
       
   128         languages.add("jscript");                
       
   129     }
       
   130 
       
   131     return languages.contains(language);
       
   132 }
       
   133 
       
   134 // ScriptElementData
       
   135 ScriptElementData::ScriptElementData(ScriptElement* scriptElement, Element* element)
       
   136     : m_scriptElement(scriptElement)
       
   137     , m_element(element)
       
   138     , m_cachedScript(0)
       
   139     , m_createdByParser(false)
       
   140     , m_requested(false)
       
   141     , m_evaluated(false)
       
   142     , m_firedLoad(false)
       
   143 {
       
   144     ASSERT(m_scriptElement);
       
   145     ASSERT(m_element);
       
   146 }
       
   147 
       
   148 ScriptElementData::~ScriptElementData()
       
   149 {
       
   150     stopLoadRequest();
       
   151 }
       
   152 
       
   153 void ScriptElementData::requestScript(const String& sourceUrl)
       
   154 {
       
   155     Document* document = m_element->document();
       
   156 
       
   157     // FIXME: Eventually we'd like to evaluate scripts which are inserted into a 
       
   158     // viewless document but this'll do for now.
       
   159     // See http://bugs.webkit.org/show_bug.cgi?id=5727
       
   160     if (!document->frame())
       
   161         return;
       
   162 
       
   163     if (!m_element->dispatchBeforeLoadEvent(sourceUrl))
       
   164         return;
       
   165 
       
   166     ASSERT(!m_cachedScript);
       
   167     m_cachedScript = document->docLoader()->requestScript(sourceUrl, scriptCharset());
       
   168     m_requested = true;
       
   169 
       
   170     // m_createdByParser is never reset - always resied at the initial value set while parsing.
       
   171     // m_evaluated is left untouched as well to avoid script reexecution, if a <script> element
       
   172     // is removed and reappended to the document.
       
   173     m_firedLoad = false;
       
   174 
       
   175     if (m_cachedScript) {
       
   176         m_cachedScript->addClient(this);
       
   177         return;
       
   178     }
       
   179 
       
   180     m_scriptElement->dispatchErrorEvent();
       
   181 }
       
   182 
       
   183 void ScriptElementData::evaluateScript(const ScriptSourceCode& sourceCode)
       
   184 {
       
   185     if (m_evaluated || sourceCode.isEmpty() || !shouldExecuteAsJavaScript())
       
   186         return;
       
   187 
       
   188     if (Frame* frame = m_element->document()->frame()) {
       
   189         if (!frame->script()->canExecuteScripts(AboutToExecuteScript))
       
   190             return;
       
   191 
       
   192         m_evaluated = true;
       
   193 
       
   194         frame->script()->evaluate(sourceCode);
       
   195         Document::updateStyleForAllDocuments();
       
   196     }
       
   197 }
       
   198 
       
   199 void ScriptElementData::stopLoadRequest()
       
   200 {
       
   201     if (m_cachedScript) {
       
   202         m_cachedScript->removeClient(this);
       
   203         m_cachedScript = 0;
       
   204     }
       
   205 }
       
   206 
       
   207 void ScriptElementData::execute(CachedScript* cachedScript)
       
   208 {
       
   209     ASSERT(cachedScript);
       
   210     if (cachedScript->errorOccurred())
       
   211         m_scriptElement->dispatchErrorEvent();
       
   212     else {
       
   213         evaluateScript(ScriptSourceCode(cachedScript));
       
   214         m_scriptElement->dispatchLoadEvent();
       
   215     }
       
   216     cachedScript->removeClient(this);
       
   217 }
       
   218 
       
   219 void ScriptElementData::notifyFinished(CachedResource* o)
       
   220 {
       
   221     ASSERT_UNUSED(o, o == m_cachedScript);
       
   222     m_element->document()->executeScriptSoon(this, m_cachedScript);
       
   223     m_cachedScript = 0;
       
   224 }
       
   225 
       
   226 bool ScriptElementData::ignoresLoadRequest() const
       
   227 {
       
   228     return m_evaluated || m_requested || m_createdByParser || !m_element->inDocument();
       
   229 }
       
   230 
       
   231 bool ScriptElementData::shouldExecuteAsJavaScript() const
       
   232 {
       
   233     /*
       
   234          Mozilla 1.8 accepts javascript1.0 - javascript1.7, but WinIE 7 accepts only javascript1.1 - javascript1.3.
       
   235          Mozilla 1.8 and WinIE 7 both accept javascript and livescript.
       
   236          WinIE 7 accepts ecmascript and jscript, but Mozilla 1.8 doesn't.
       
   237          Neither Mozilla 1.8 nor WinIE 7 accept leading or trailing whitespace.
       
   238          We want to accept all the values that either of these browsers accept, but not other values.
       
   239      */
       
   240     String type = m_scriptElement->typeAttributeValue();
       
   241     if (!type.isEmpty()) {
       
   242         if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace().lower()))
       
   243             return false;
       
   244     } else {
       
   245         String language = m_scriptElement->languageAttributeValue();
       
   246         if (!language.isEmpty() && !isSupportedJavaScriptLanguage(language))
       
   247             return false;
       
   248     }    
       
   249 
       
   250     // No type or language is specified, so we assume the script to be JavaScript.
       
   251     // We don't yet support setting event listeners via the 'for' attribute for scripts.
       
   252     // If there is such an attribute it's likely better to not execute the script than to do so
       
   253     // immediately and unconditionally.
       
   254     // FIXME: After <rdar://problem/4471751> / https://bugs.webkit.org/show_bug.cgi?id=16915 are resolved 
       
   255     // and we support the for syntax in script tags, this check can be removed and we should just
       
   256     // return 'true' here.
       
   257     String forAttribute = m_scriptElement->forAttributeValue();
       
   258     String eventAttribute = m_scriptElement->eventAttributeValue();
       
   259     if (forAttribute.isEmpty() || eventAttribute.isEmpty())
       
   260         return true;
       
   261     
       
   262     forAttribute = forAttribute.stripWhiteSpace();
       
   263     eventAttribute = eventAttribute.stripWhiteSpace();
       
   264     return equalIgnoringCase(forAttribute, "window") && (equalIgnoringCase(eventAttribute, "onload") || equalIgnoringCase(eventAttribute, "onload()"));
       
   265 }
       
   266 
       
   267 String ScriptElementData::scriptCharset() const
       
   268 {
       
   269     // First we try to get encoding from charset attribute.
       
   270     String charset = m_scriptElement->charsetAttributeValue().stripWhiteSpace();
       
   271 
       
   272     // If charset has not been declared in script tag, fall back to frame encoding.
       
   273     if (charset.isEmpty()) {
       
   274         if (Frame* frame = m_element->document()->frame())
       
   275             charset = frame->loader()->writer()->encoding();
       
   276     }
       
   277 
       
   278     return charset;
       
   279 }
       
   280 
       
   281 String ScriptElementData::scriptContent() const
       
   282 {
       
   283     Vector<UChar> val;
       
   284     Text* firstTextNode = 0;
       
   285     bool foundMultipleTextNodes = false;
       
   286 
       
   287     for (Node* n = m_element->firstChild(); n; n = n->nextSibling()) {
       
   288         if (!n->isTextNode())
       
   289             continue;
       
   290 
       
   291         Text* t = static_cast<Text*>(n);
       
   292         if (foundMultipleTextNodes)
       
   293             append(val, t->data());
       
   294         else if (firstTextNode) {
       
   295             append(val, firstTextNode->data());
       
   296             append(val, t->data());
       
   297             foundMultipleTextNodes = true;
       
   298         } else
       
   299             firstTextNode = t;
       
   300     }
       
   301 
       
   302     if (firstTextNode && !foundMultipleTextNodes)
       
   303         return firstTextNode->data();
       
   304 
       
   305     return String::adopt(val);
       
   306 }
       
   307 
       
   308 bool ScriptElementData::isAsynchronous() const
       
   309 {
       
   310     // Only external scripts may be asynchronous.
       
   311     // See: http://dev.w3.org/html5/spec/Overview.html#attr-script-async
       
   312     return !m_scriptElement->sourceAttributeValue().isEmpty() && m_scriptElement->asyncAttributeValue();
       
   313 }
       
   314 
       
   315 bool ScriptElementData::isDeferred() const
       
   316 {
       
   317     // Only external scripts may be deferred and async trumps defer to allow for backward compatibility.
       
   318     // See: http://dev.w3.org/html5/spec/Overview.html#attr-script-defer
       
   319     return !m_scriptElement->sourceAttributeValue().isEmpty() && !m_scriptElement->asyncAttributeValue() && m_scriptElement->deferAttributeValue();
       
   320 }
       
   321 
       
   322 ScriptElement* toScriptElement(Element* element)
       
   323 {
       
   324     if (element->isHTMLElement() && element->hasTagName(HTMLNames::scriptTag))
       
   325         return static_cast<HTMLScriptElement*>(element);
       
   326 
       
   327 #if ENABLE(SVG)
       
   328     if (element->isSVGElement() && element->hasTagName(SVGNames::scriptTag))
       
   329         return static_cast<SVGScriptElement*>(element);
       
   330 #endif
       
   331 
       
   332     return 0;
       
   333 }
       
   334 
       
   335 }