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 "HTMLParserIdioms.h"
36 #include "HTMLScriptElement.h"
37 #include "IgnoreDestructiveWriteCountIncrementer.h"
38 #include "MIMETypeRegistry.h"
40 #include "ScriptRunner.h"
41 #include "ScriptSourceCode.h"
42 #include "ScriptValue.h"
45 #include <wtf/StdLibExtras.h>
46 #include <wtf/text/StringBuilder.h>
47 #include <wtf/text/StringHash.h>
51 #include "SVGScriptElement.h"
56 ScriptElement::ScriptElement(Element* element, bool parserInserted, bool alreadyStarted)
59 , m_parserInserted(parserInserted)
60 , m_isExternalScript(false)
61 , m_alreadyStarted(alreadyStarted)
62 , m_haveFiredLoad(false)
63 , m_willBeParserExecuted(false)
64 , m_readyToBeParserExecuted(false)
65 , m_willExecuteWhenDocumentFinishedParsing(false)
66 , m_forceAsync(!parserInserted)
67 , m_willExecuteInOrder(false)
68 , m_cachedScriptState(NeverSet)
73 ScriptElement::~ScriptElement()
78 void ScriptElement::insertedIntoDocument()
80 if (!m_parserInserted)
81 prepareScript(); // FIXME: Provide a real starting line number here.
84 void ScriptElement::removedFromDocument()
86 // Eventually stop loading any not-yet-finished content
90 void ScriptElement::childrenChanged()
92 if (!m_parserInserted && m_element->inDocument())
93 prepareScript(); // FIXME: Provide a real starting line number here.
96 void ScriptElement::handleSourceAttribute(const String& sourceUrl)
98 if (ignoresLoadRequest() || sourceUrl.isEmpty())
101 prepareScript(); // FIXME: Provide a real starting line number here.
104 void ScriptElement::handleAsyncAttribute()
106 m_forceAsync = false;
110 static bool isLegacySupportedJavaScriptLanguage(const String& language)
112 // Mozilla 1.8 accepts javascript1.0 - javascript1.7, but WinIE 7 accepts only javascript1.1 - javascript1.3.
113 // Mozilla 1.8 and WinIE 7 both accept javascript and livescript.
114 // WinIE 7 accepts ecmascript and jscript, but Mozilla 1.8 doesn't.
115 // Neither Mozilla 1.8 nor WinIE 7 accept leading or trailing whitespace.
116 // We want to accept all the values that either of these browsers accept, but not other values.
118 // FIXME: This function is not HTML5 compliant. These belong in the MIME registry as "text/javascript<version>" entries.
119 typedef HashSet<String, CaseFoldingHash> LanguageSet;
120 DEFINE_STATIC_LOCAL(LanguageSet, languages, ());
121 if (languages.isEmpty()) {
122 languages.add("javascript");
123 languages.add("javascript");
124 languages.add("javascript1.0");
125 languages.add("javascript1.1");
126 languages.add("javascript1.2");
127 languages.add("javascript1.3");
128 languages.add("javascript1.4");
129 languages.add("javascript1.5");
130 languages.add("javascript1.6");
131 languages.add("javascript1.7");
132 languages.add("livescript");
133 languages.add("ecmascript");
134 languages.add("jscript");
137 return languages.contains(language);
140 void ScriptElement::dispatchErrorEvent()
142 m_element->dispatchEvent(Event::create(eventNames().errorEvent, false, false));
145 bool ScriptElement::isScriptTypeSupported(LegacyTypeSupport supportLegacyTypes) const
147 // FIXME: isLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used here to maintain backwards compatibility with existing layout tests. The specific violations are:
148 // - Allowing type=javascript. type= should only support MIME types, such as text/javascript.
149 // - Allowing a different set of languages for language= and type=. language= supports Javascript 1.1 and 1.4-1.6, but type= does not.
151 String type = typeAttributeValue();
152 String language = languageAttributeValue();
153 if (type.isEmpty() && language.isEmpty())
154 return true; // Assume text/javascript.
155 if (type.isEmpty()) {
156 type = "text/" + language.lower();
157 if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type) || isLegacySupportedJavaScriptLanguage(language))
159 } else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace().lower()) || (supportLegacyTypes == AllowLegacyTypeInTypeAttribute && isLegacySupportedJavaScriptLanguage(type)))
164 // http://dev.w3.org/html5/spec/Overview.html#prepare-a-script
165 bool ScriptElement::prepareScript(const TextPosition1& scriptStartPosition, LegacyTypeSupport supportLegacyTypes)
167 if (m_alreadyStarted)
170 bool wasParserInserted;
171 if (m_parserInserted) {
172 wasParserInserted = true;
173 m_parserInserted = false;
175 wasParserInserted = false;
177 if (wasParserInserted && !asyncAttributeValue())
180 // FIXME: HTML5 spec says we should check that all children are either comments or empty text nodes.
181 if (!hasSourceAttribute() && !m_element->firstChild())
184 if (!m_element->inDocument())
187 if (!isScriptTypeSupported(supportLegacyTypes))
190 if (wasParserInserted) {
191 m_parserInserted = true;
192 m_forceAsync = false;
195 m_alreadyStarted = true;
197 // FIXME: If script is parser inserted, verify it's still in the original document.
199 // FIXME: Eventually we'd like to evaluate scripts which are inserted into a
200 // viewless document but this'll do for now.
201 // See http://bugs.webkit.org/show_bug.cgi?id=5727
202 if (!m_element->document()->frame())
205 if (!m_element->document()->frame()->script()->canExecuteScripts(AboutToExecuteScript))
208 Node* ancestor = m_element->parentNode();
210 if (ancestor->isSVGShadowRoot()) {
211 fprintf(stderr, "aborted script: shadow root\n");
214 ancestor = ancestor->parentNode();
217 if (!isScriptForEventSupported())
220 if (!charsetAttributeValue().isEmpty())
221 m_characterEncoding = charsetAttributeValue();
223 m_characterEncoding = m_element->document()->charset();
225 if (hasSourceAttribute())
226 if (!requestScript(sourceAttributeValue()))
229 if (hasSourceAttribute() && deferAttributeValue() && m_parserInserted && !asyncAttributeValue()) {
230 m_willExecuteWhenDocumentFinishedParsing = true;
231 m_willBeParserExecuted = true;
232 } else if (hasSourceAttribute() && m_parserInserted && !asyncAttributeValue())
233 m_willBeParserExecuted = true;
234 else if (!hasSourceAttribute() && m_parserInserted && !m_element->document()->haveStylesheetsLoaded()) {
235 m_willBeParserExecuted = true;
236 m_readyToBeParserExecuted = true;
237 } else if (hasSourceAttribute() && !asyncAttributeValue() && !m_forceAsync) {
238 m_willExecuteInOrder = true;
239 m_element->document()->scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::IN_ORDER_EXECUTION);
240 m_cachedScript->addClient(this);
241 } else if (hasSourceAttribute())
242 m_cachedScript->addClient(this);
244 executeScript(ScriptSourceCode(scriptContent(), m_element->document()->url(), scriptStartPosition));
249 bool ScriptElement::requestScript(const String& sourceUrl)
251 RefPtr<Document> originalDocument = m_element->document();
252 if (!m_element->dispatchBeforeLoadEvent(sourceUrl))
254 if (!m_element->inDocument() || m_element->document() != originalDocument)
257 ASSERT(!m_cachedScript);
258 if (!stripLeadingAndTrailingHTMLSpaces(sourceUrl).isEmpty()) {
259 ResourceRequest request(m_element->document()->completeURL(sourceUrl));
260 m_cachedScript = m_element->document()->cachedResourceLoader()->requestScript(request, scriptCharset());
261 m_isExternalScript = true;
264 if (m_cachedScript) {
265 ASSERT(m_cachedScriptState == NeverSet);
266 m_cachedScriptState = Set;
270 dispatchErrorEvent();
274 void ScriptElement::executeScript(const ScriptSourceCode& sourceCode)
276 ASSERT(m_alreadyStarted);
278 if (sourceCode.isEmpty())
281 if (!m_isExternalScript && !m_element->document()->contentSecurityPolicy()->allowInlineScript())
284 RefPtr<Document> document = m_element->document();
286 if (Frame* frame = document->frame()) {
288 IgnoreDestructiveWriteCountIncrementer ignoreDesctructiveWriteCountIncrementer(m_isExternalScript ? document.get() : 0);
289 // Create a script from the script element node, using the script
290 // block's source and the script block's type.
291 // Note: This is where the script is compiled and actually executed.
292 frame->script()->evaluate(sourceCode);
295 Document::updateStyleForAllDocuments();
299 void ScriptElement::stopLoadRequest()
301 if (m_cachedScript) {
302 if (!m_willBeParserExecuted)
303 m_cachedScript->removeClient(this);
304 ASSERT(m_cachedScriptState == Set);
305 m_cachedScriptState = ZeroedInStopLoadRequest;
310 void ScriptElement::execute(CachedScript* cachedScript)
312 ASSERT(!m_willBeParserExecuted);
313 ASSERT(cachedScript);
314 if (cachedScript->errorOccurred())
315 dispatchErrorEvent();
316 else if (!cachedScript->wasCanceled()) {
317 executeScript(ScriptSourceCode(cachedScript));
320 cachedScript->removeClient(this);
323 void ScriptElement::notifyFinished(CachedResource* o)
325 ASSERT(!m_willBeParserExecuted);
326 ASSERT_UNUSED(o, o == m_cachedScript);
327 if (m_willExecuteInOrder)
328 m_element->document()->scriptRunner()->notifyInOrderScriptReady();
330 m_element->document()->scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::ASYNC_EXECUTION);
332 ASSERT(m_cachedScriptState == Set);
333 m_cachedScriptState = ZeroedInNotifyFinished;
337 bool ScriptElement::ignoresLoadRequest() const
339 return m_alreadyStarted || m_isExternalScript || m_parserInserted || !m_element->inDocument();
342 bool ScriptElement::isScriptForEventSupported() const
344 String eventAttribute = eventAttributeValue();
345 String forAttribute = forAttributeValue();
346 if (!eventAttribute.isEmpty() && !forAttribute.isEmpty()) {
347 forAttribute = forAttribute.stripWhiteSpace();
348 if (!equalIgnoringCase(forAttribute, "window"))
351 eventAttribute = eventAttribute.stripWhiteSpace();
352 if (!equalIgnoringCase(eventAttribute, "onload") && !equalIgnoringCase(eventAttribute, "onload()"))
358 String ScriptElement::scriptContent() const
360 StringBuilder content;
361 Text* firstTextNode = 0;
362 bool foundMultipleTextNodes = false;
364 for (Node* n = m_element->firstChild(); n; n = n->nextSibling()) {
365 if (!n->isTextNode())
368 Text* t = static_cast<Text*>(n);
369 if (foundMultipleTextNodes)
370 content.append(t->data());
371 else if (firstTextNode) {
372 content.append(firstTextNode->data());
373 content.append(t->data());
374 foundMultipleTextNodes = true;
379 if (firstTextNode && !foundMultipleTextNodes)
380 return firstTextNode->data();
382 return content.toString();
385 ScriptElement* toScriptElement(Element* element)
387 if (element->isHTMLElement() && element->hasTagName(HTMLNames::scriptTag))
388 return static_cast<HTMLScriptElement*>(element);
391 if (element->isSVGElement() && element->hasTagName(SVGNames::scriptTag))
392 return static_cast<SVGScriptElement*>(element);