Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2001 Dirk Mueller (mueller (at) kde.org)
      5  * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
      6  * Copyright (C) 2008 Nikolas Zimmermann <zimmermann (at) 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 "CachedResourceLoader.h"
     29 #include "ContentSecurityPolicy.h"
     30 #include "Document.h"
     31 #include "DocumentParser.h"
     32 #include "Frame.h"
     33 #include "FrameLoader.h"
     34 #include "HTMLNames.h"
     35 #include "HTMLScriptElement.h"
     36 #include "IgnoreDestructiveWriteCountIncrementer.h"
     37 #include "MIMETypeRegistry.h"
     38 #include "Page.h"
     39 #include "ScriptController.h"
     40 #include "ScriptRunner.h"
     41 #include "ScriptSourceCode.h"
     42 #include "ScriptValue.h"
     43 #include "Settings.h"
     44 #include "Text.h"
     45 #include <wtf/StdLibExtras.h>
     46 #include <wtf/text/StringHash.h>
     47 
     48 #if ENABLE(SVG)
     49 #include "SVGNames.h"
     50 #include "SVGScriptElement.h"
     51 #endif
     52 
     53 namespace WebCore {
     54 
     55 ScriptElement::ScriptElement(Element* element, bool parserInserted, bool alreadyStarted)
     56     : m_element(element)
     57     , m_cachedScript(0)
     58     , m_parserInserted(parserInserted)
     59     , m_isExternalScript(false)
     60     , m_alreadyStarted(alreadyStarted)
     61     , m_haveFiredLoad(false)
     62     , m_willBeParserExecuted(false)
     63     , m_readyToBeParserExecuted(false)
     64     , m_willExecuteWhenDocumentFinishedParsing(false)
     65     , m_forceAsync(!parserInserted)
     66     , m_willExecuteInOrder(false)
     67 {
     68     ASSERT(m_element);
     69 }
     70 
     71 ScriptElement::~ScriptElement()
     72 {
     73     stopLoadRequest();
     74 }
     75 
     76 void ScriptElement::insertedIntoDocument()
     77 {
     78     if (!m_parserInserted)
     79         prepareScript(); // FIXME: Provide a real starting line number here.
     80 }
     81 
     82 void ScriptElement::removedFromDocument()
     83 {
     84     // Eventually stop loading any not-yet-finished content
     85     stopLoadRequest();
     86 }
     87 
     88 void ScriptElement::childrenChanged()
     89 {
     90     if (!m_parserInserted && m_element->inDocument())
     91         prepareScript(); // FIXME: Provide a real starting line number here.
     92 }
     93 
     94 void ScriptElement::handleSourceAttribute(const String& sourceUrl)
     95 {
     96     if (ignoresLoadRequest() || sourceUrl.isEmpty())
     97         return;
     98 
     99     prepareScript(); // FIXME: Provide a real starting line number here.
    100 }
    101 
    102 void ScriptElement::handleAsyncAttribute()
    103 {
    104     m_forceAsync = false;
    105 }
    106 
    107 // Helper function
    108 static bool isLegacySupportedJavaScriptLanguage(const String& language)
    109 {
    110     // Mozilla 1.8 accepts javascript1.0 - javascript1.7, but WinIE 7 accepts only javascript1.1 - javascript1.3.
    111     // Mozilla 1.8 and WinIE 7 both accept javascript and livescript.
    112     // WinIE 7 accepts ecmascript and jscript, but Mozilla 1.8 doesn't.
    113     // Neither Mozilla 1.8 nor WinIE 7 accept leading or trailing whitespace.
    114     // We want to accept all the values that either of these browsers accept, but not other values.
    115 
    116     // FIXME: This function is not HTML5 compliant. These belong in the MIME registry as "text/javascript<version>" entries.
    117     typedef HashSet<String, CaseFoldingHash> LanguageSet;
    118     DEFINE_STATIC_LOCAL(LanguageSet, languages, ());
    119     if (languages.isEmpty()) {
    120         languages.add("javascript");
    121         languages.add("javascript");
    122         languages.add("javascript1.0");
    123         languages.add("javascript1.1");
    124         languages.add("javascript1.2");
    125         languages.add("javascript1.3");
    126         languages.add("javascript1.4");
    127         languages.add("javascript1.5");
    128         languages.add("javascript1.6");
    129         languages.add("javascript1.7");
    130         languages.add("livescript");
    131         languages.add("ecmascript");
    132         languages.add("jscript");
    133     }
    134 
    135     return languages.contains(language);
    136 }
    137 
    138 bool ScriptElement::isScriptTypeSupported(LegacyTypeSupport supportLegacyTypes) const
    139 {
    140     // FIXME: isLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used here to maintain backwards compatibility with existing layout tests. The specific violations are:
    141     // - Allowing type=javascript. type= should only support MIME types, such as text/javascript.
    142     // - Allowing a different set of languages for language= and type=. language= supports Javascript 1.1 and 1.4-1.6, but type= does not.
    143 
    144     String type = typeAttributeValue();
    145     String language = languageAttributeValue();
    146     if (type.isEmpty() && language.isEmpty())
    147         return true; // Assume text/javascript.
    148     if (type.isEmpty()) {
    149         type = "text/" + language.lower();
    150         if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type) || isLegacySupportedJavaScriptLanguage(language))
    151             return true;
    152     } else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace().lower()) || (supportLegacyTypes == AllowLegacyTypeInTypeAttribute && isLegacySupportedJavaScriptLanguage(type)))
    153         return true;
    154     return false;
    155 }
    156 
    157 // http://dev.w3.org/html5/spec/Overview.html#prepare-a-script
    158 bool ScriptElement::prepareScript(const TextPosition1& scriptStartPosition, LegacyTypeSupport supportLegacyTypes)
    159 {
    160     if (m_alreadyStarted)
    161         return false;
    162 
    163     bool wasParserInserted;
    164     if (m_parserInserted) {
    165         wasParserInserted = true;
    166         m_parserInserted = false;
    167     } else
    168         wasParserInserted = false;
    169 
    170     if (wasParserInserted && !asyncAttributeValue())
    171         m_forceAsync = true;
    172 
    173     // FIXME: HTML5 spec says we should check that all children are either comments or empty text nodes.
    174     if (!hasSourceAttribute() && !m_element->firstChild())
    175         return false;
    176 
    177     if (!m_element->inDocument())
    178         return false;
    179 
    180     if (!isScriptTypeSupported(supportLegacyTypes))
    181         return false;
    182 
    183     if (wasParserInserted) {
    184         m_parserInserted = true;
    185         m_forceAsync = false;
    186     }
    187 
    188     m_alreadyStarted = true;
    189 
    190     // FIXME: If script is parser inserted, verify it's still in the original document.
    191 
    192     // FIXME: Eventually we'd like to evaluate scripts which are inserted into a
    193     // viewless document but this'll do for now.
    194     // See http://bugs.webkit.org/show_bug.cgi?id=5727
    195     if (!m_element->document()->frame())
    196         return false;
    197 
    198     if (!m_element->document()->frame()->script()->canExecuteScripts(AboutToExecuteScript))
    199         return false;
    200 
    201     // FIXME: This is non-standard. Remove this after https://bugs.webkit.org/show_bug.cgi?id=62412.
    202     Node* ancestor = m_element->parentNode();
    203     while (ancestor) {
    204         if (ancestor->isSVGShadowRoot())
    205             return false;
    206         ancestor = ancestor->parentNode();
    207     }
    208 
    209     if (!isScriptForEventSupported())
    210         return false;
    211 
    212     if (!charsetAttributeValue().isEmpty())
    213         m_characterEncoding = charsetAttributeValue();
    214     else
    215         m_characterEncoding = m_element->document()->charset();
    216 
    217     if (hasSourceAttribute())
    218         if (!requestScript(sourceAttributeValue()))
    219             return false;
    220 
    221     if (hasSourceAttribute() && deferAttributeValue() && m_parserInserted && !asyncAttributeValue()) {
    222         m_willExecuteWhenDocumentFinishedParsing = true;
    223         m_willBeParserExecuted = true;
    224     } else if (hasSourceAttribute() && m_parserInserted && !asyncAttributeValue())
    225         m_willBeParserExecuted = true;
    226     else if (!hasSourceAttribute() && m_parserInserted && !m_element->document()->haveStylesheetsLoaded()) {
    227         m_willBeParserExecuted = true;
    228         m_readyToBeParserExecuted = true;
    229     } else if (hasSourceAttribute() && !asyncAttributeValue() && !m_forceAsync) {
    230         m_willExecuteInOrder = true;
    231         m_element->document()->scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::IN_ORDER_EXECUTION);
    232         m_cachedScript->addClient(this);
    233     } else if (hasSourceAttribute())
    234         m_cachedScript->addClient(this);
    235     else
    236         executeScript(ScriptSourceCode(scriptContent(), m_element->document()->url(), scriptStartPosition));
    237 
    238     return true;
    239 }
    240 
    241 bool ScriptElement::requestScript(const String& sourceUrl)
    242 {
    243     RefPtr<Document> originalDocument = m_element->document();
    244     if (!m_element->dispatchBeforeLoadEvent(sourceUrl))
    245         return false;
    246     if (!m_element->inDocument() || m_element->document() != originalDocument)
    247         return false;
    248 
    249     ASSERT(!m_cachedScript);
    250     // FIXME: If sourceUrl is empty, we should dispatchErrorEvent().
    251     m_cachedScript = m_element->document()->cachedResourceLoader()->requestScript(sourceUrl, scriptCharset());
    252     m_isExternalScript = true;
    253 
    254     if (m_cachedScript)
    255         return true;
    256 
    257     dispatchErrorEvent();
    258     return false;
    259 }
    260 
    261 void ScriptElement::executeScript(const ScriptSourceCode& sourceCode)
    262 {
    263     ASSERT(m_alreadyStarted);
    264 
    265     if (sourceCode.isEmpty())
    266         return;
    267 
    268     if (!m_isExternalScript && !m_element->document()->contentSecurityPolicy()->allowInlineScript())
    269         return;
    270 
    271     RefPtr<Document> document = m_element->document();
    272     ASSERT(document);
    273     if (Frame* frame = document->frame()) {
    274         {
    275             IgnoreDestructiveWriteCountIncrementer ignoreDesctructiveWriteCountIncrementer(m_isExternalScript ? document.get() : 0);
    276             // Create a script from the script element node, using the script
    277             // block's source and the script block's type.
    278             // Note: This is where the script is compiled and actually executed.
    279             frame->script()->evaluate(sourceCode);
    280         }
    281 
    282         Document::updateStyleForAllDocuments();
    283     }
    284 }
    285 
    286 void ScriptElement::stopLoadRequest()
    287 {
    288     if (m_cachedScript) {
    289         if (!m_willBeParserExecuted)
    290             m_cachedScript->removeClient(this);
    291         m_cachedScript = 0;
    292     }
    293 }
    294 
    295 void ScriptElement::execute(CachedScript* cachedScript)
    296 {
    297     ASSERT(!m_willBeParserExecuted);
    298     ASSERT(cachedScript);
    299     if (cachedScript->errorOccurred())
    300         dispatchErrorEvent();
    301     else {
    302         executeScript(ScriptSourceCode(cachedScript));
    303         dispatchLoadEvent();
    304     }
    305     cachedScript->removeClient(this);
    306 }
    307 
    308 void ScriptElement::notifyFinished(CachedResource* o)
    309 {
    310     ASSERT(!m_willBeParserExecuted);
    311     ASSERT_UNUSED(o, o == m_cachedScript);
    312     if (m_willExecuteInOrder)
    313         m_element->document()->scriptRunner()->notifyInOrderScriptReady();
    314     else
    315         m_element->document()->scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::ASYNC_EXECUTION);
    316     m_cachedScript = 0;
    317 }
    318 
    319 bool ScriptElement::ignoresLoadRequest() const
    320 {
    321     return m_alreadyStarted || m_isExternalScript || m_parserInserted || !m_element->inDocument();
    322 }
    323 
    324 bool ScriptElement::isScriptForEventSupported() const
    325 {
    326     String eventAttribute = eventAttributeValue();
    327     String forAttribute = forAttributeValue();
    328     if (!eventAttribute.isEmpty() && !forAttribute.isEmpty()) {
    329         forAttribute = forAttribute.stripWhiteSpace();
    330         if (!equalIgnoringCase(forAttribute, "window"))
    331             return false;
    332 
    333         eventAttribute = eventAttribute.stripWhiteSpace();
    334         if (!equalIgnoringCase(eventAttribute, "onload") && !equalIgnoringCase(eventAttribute, "onload()"))
    335             return false;
    336     }
    337     return true;
    338 }
    339 
    340 String ScriptElement::scriptContent() const
    341 {
    342     Vector<UChar> val;
    343     Text* firstTextNode = 0;
    344     bool foundMultipleTextNodes = false;
    345 
    346     for (Node* n = m_element->firstChild(); n; n = n->nextSibling()) {
    347         if (!n->isTextNode())
    348             continue;
    349 
    350         Text* t = static_cast<Text*>(n);
    351         if (foundMultipleTextNodes)
    352             append(val, t->data());
    353         else if (firstTextNode) {
    354             append(val, firstTextNode->data());
    355             append(val, t->data());
    356             foundMultipleTextNodes = true;
    357         } else
    358             firstTextNode = t;
    359     }
    360 
    361     if (firstTextNode && !foundMultipleTextNodes)
    362         return firstTextNode->data();
    363 
    364     return String::adopt(val);
    365 }
    366 
    367 ScriptElement* toScriptElement(Element* element)
    368 {
    369     if (element->isHTMLElement() && element->hasTagName(HTMLNames::scriptTag))
    370         return static_cast<HTMLScriptElement*>(element);
    371 
    372 #if ENABLE(SVG)
    373     if (element->isSVGElement() && element->hasTagName(SVGNames::scriptTag))
    374         return static_cast<SVGScriptElement*>(element);
    375 #endif
    376 
    377     return 0;
    378 }
    379 
    380 }
    381