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>
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.
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.
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.
25 #include "ScriptElement.h"
27 #include "CachedScript.h"
28 #include "CachedResourceLoader.h"
29 #include "ContentSecurityPolicy.h"
31 #include "DocumentParser.h"
33 #include "FrameLoader.h"
34 #include "HTMLNames.h"
35 #include "HTMLScriptElement.h"
36 #include "IgnoreDestructiveWriteCountIncrementer.h"
37 #include "MIMETypeRegistry.h"
39 #include "ScriptController.h"
40 #include "ScriptRunner.h"
41 #include "ScriptSourceCode.h"
42 #include "ScriptValue.h"
45 #include <wtf/StdLibExtras.h>
46 #include <wtf/text/StringHash.h>
50 #include "SVGScriptElement.h"
55 ScriptElement::ScriptElement(Element* element, bool parserInserted, bool alreadyStarted)
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)
71 ScriptElement::~ScriptElement()
76 void ScriptElement::insertedIntoDocument()
78 if (!m_parserInserted)
79 prepareScript(); // FIXME: Provide a real starting line number here.
82 void ScriptElement::removedFromDocument()
84 // Eventually stop loading any not-yet-finished content
88 void ScriptElement::childrenChanged()
90 if (!m_parserInserted && m_element->inDocument())
91 prepareScript(); // FIXME: Provide a real starting line number here.
94 void ScriptElement::handleSourceAttribute(const String& sourceUrl)
96 if (ignoresLoadRequest() || sourceUrl.isEmpty())
99 prepareScript(); // FIXME: Provide a real starting line number here.
102 void ScriptElement::handleAsyncAttribute()
104 m_forceAsync = false;
108 static bool isLegacySupportedJavaScriptLanguage(const String& language)
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.
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");
135 return languages.contains(language);
138 bool ScriptElement::isScriptTypeSupported(LegacyTypeSupport supportLegacyTypes) const
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.
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))
152 } else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace().lower()) || (supportLegacyTypes == AllowLegacyTypeInTypeAttribute && isLegacySupportedJavaScriptLanguage(type)))
157 // http://dev.w3.org/html5/spec/Overview.html#prepare-a-script
158 bool ScriptElement::prepareScript(const TextPosition1& scriptStartPosition, LegacyTypeSupport supportLegacyTypes)
160 if (m_alreadyStarted)
163 bool wasParserInserted;
164 if (m_parserInserted) {
165 wasParserInserted = true;
166 m_parserInserted = false;
168 wasParserInserted = false;
170 if (wasParserInserted && !asyncAttributeValue())
173 // FIXME: HTML5 spec says we should check that all children are either comments or empty text nodes.
174 if (!hasSourceAttribute() && !m_element->firstChild())
177 if (!m_element->inDocument())
180 if (!isScriptTypeSupported(supportLegacyTypes))
183 if (wasParserInserted) {
184 m_parserInserted = true;
185 m_forceAsync = false;
188 m_alreadyStarted = true;
190 // FIXME: If script is parser inserted, verify it's still in the original document.
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())
198 if (!m_element->document()->frame()->script()->canExecuteScripts(AboutToExecuteScript))
201 if (!isScriptForEventSupported())
204 if (!charsetAttributeValue().isEmpty())
205 m_characterEncoding = charsetAttributeValue();
207 m_characterEncoding = m_element->document()->charset();
209 if (hasSourceAttribute())
210 if (!requestScript(sourceAttributeValue()))
213 if (hasSourceAttribute() && deferAttributeValue() && m_parserInserted && !asyncAttributeValue()) {
214 m_willExecuteWhenDocumentFinishedParsing = true;
215 m_willBeParserExecuted = true;
216 } else if (hasSourceAttribute() && m_parserInserted && !asyncAttributeValue())
217 m_willBeParserExecuted = true;
218 else if (!hasSourceAttribute() && m_parserInserted && !m_element->document()->haveStylesheetsLoaded()) {
219 m_willBeParserExecuted = true;
220 m_readyToBeParserExecuted = true;
221 } else if (hasSourceAttribute() && !asyncAttributeValue() && !m_forceAsync) {
222 m_willExecuteInOrder = true;
223 m_element->document()->scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::IN_ORDER_EXECUTION);
224 m_cachedScript->addClient(this);
225 } else if (hasSourceAttribute())
226 m_cachedScript->addClient(this);
228 executeScript(ScriptSourceCode(scriptContent(), m_element->document()->url(), scriptStartPosition));
233 bool ScriptElement::requestScript(const String& sourceUrl)
235 RefPtr<Document> originalDocument = m_element->document();
236 if (!m_element->dispatchBeforeLoadEvent(sourceUrl))
238 if (!m_element->inDocument() || m_element->document() != originalDocument)
241 ASSERT(!m_cachedScript);
242 // FIXME: If sourceUrl is empty, we should dispatchErrorEvent().
243 m_cachedScript = m_element->document()->cachedResourceLoader()->requestScript(sourceUrl, scriptCharset());
244 m_isExternalScript = true;
249 dispatchErrorEvent();
253 void ScriptElement::executeScript(const ScriptSourceCode& sourceCode)
255 ASSERT(m_alreadyStarted);
257 if (sourceCode.isEmpty())
260 if (!m_isExternalScript && !m_element->document()->contentSecurityPolicy()->allowInlineScript())
263 RefPtr<Document> document = m_element->document();
265 if (Frame* frame = document->frame()) {
267 IgnoreDestructiveWriteCountIncrementer ignoreDesctructiveWriteCountIncrementer(m_isExternalScript ? document.get() : 0);
268 // Create a script from the script element node, using the script
269 // block's source and the script block's type.
270 // Note: This is where the script is compiled and actually executed.
271 frame->script()->evaluate(sourceCode);
274 Document::updateStyleForAllDocuments();
278 void ScriptElement::stopLoadRequest()
280 if (m_cachedScript) {
281 if (!m_willBeParserExecuted)
282 m_cachedScript->removeClient(this);
287 void ScriptElement::execute(CachedScript* cachedScript)
289 ASSERT(!m_willBeParserExecuted);
290 ASSERT(cachedScript);
291 if (cachedScript->errorOccurred())
292 dispatchErrorEvent();
294 executeScript(ScriptSourceCode(cachedScript));
297 cachedScript->removeClient(this);
300 void ScriptElement::notifyFinished(CachedResource* o)
302 ASSERT(!m_willBeParserExecuted);
303 ASSERT_UNUSED(o, o == m_cachedScript);
304 if (m_willExecuteInOrder)
305 m_element->document()->scriptRunner()->notifyInOrderScriptReady();
307 m_element->document()->scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::ASYNC_EXECUTION);
311 bool ScriptElement::ignoresLoadRequest() const
313 return m_alreadyStarted || m_isExternalScript || m_parserInserted || !m_element->inDocument();
316 bool ScriptElement::isScriptForEventSupported() const
318 String eventAttribute = eventAttributeValue();
319 String forAttribute = forAttributeValue();
320 if (!eventAttribute.isEmpty() && !forAttribute.isEmpty()) {
321 forAttribute = forAttribute.stripWhiteSpace();
322 if (!equalIgnoringCase(forAttribute, "window"))
325 eventAttribute = eventAttribute.stripWhiteSpace();
326 if (!equalIgnoringCase(eventAttribute, "onload") && !equalIgnoringCase(eventAttribute, "onload()"))
332 String ScriptElement::scriptContent() const
335 Text* firstTextNode = 0;
336 bool foundMultipleTextNodes = false;
338 for (Node* n = m_element->firstChild(); n; n = n->nextSibling()) {
339 if (!n->isTextNode())
342 Text* t = static_cast<Text*>(n);
343 if (foundMultipleTextNodes)
344 append(val, t->data());
345 else if (firstTextNode) {
346 append(val, firstTextNode->data());
347 append(val, t->data());
348 foundMultipleTextNodes = true;
353 if (firstTextNode && !foundMultipleTextNodes)
354 return firstTextNode->data();
356 return String::adopt(val);
359 ScriptElement* toScriptElement(Element* element)
361 if (element->isHTMLElement() && element->hasTagName(HTMLNames::scriptTag))
362 return static_cast<HTMLScriptElement*>(element);
365 if (element->isSVGElement() && element->hasTagName(SVGNames::scriptTag))
366 return static_cast<SVGScriptElement*>(element);