Node: removedFrom() and insertedInto() should use references.
[WebKit-https.git] / Source / WebCore / dom / ScriptElement.cpp
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 "CachedResourceLoader.h"
29 #include "CachedResourceRequest.h"
30 #include "ContentSecurityPolicy.h"
31 #include "CrossOriginAccessControl.h"
32 #include "CurrentScriptIncrementer.h"
33 #include "Document.h"
34 #include "DocumentParser.h"
35 #include "Event.h"
36 #include "Frame.h"
37 #include "FrameLoader.h"
38 #include "HTMLNames.h"
39 #include "HTMLParserIdioms.h"
40 #include "HTMLScriptElement.h"
41 #include "IgnoreDestructiveWriteCountIncrementer.h"
42 #include "MIMETypeRegistry.h"
43 #include "Page.h"
44 #include "ScriptCallStack.h"
45 #include "ScriptController.h"
46 #include "ScriptRunner.h"
47 #include "ScriptSourceCode.h"
48 #include "ScriptValue.h"
49 #include "ScriptableDocumentParser.h"
50 #include "SecurityOrigin.h"
51 #include "Settings.h"
52 #include "Text.h"
53 #include "TextNodeTraversal.h"
54 #include <wtf/StdLibExtras.h>
55 #include <wtf/text/StringBuilder.h>
56 #include <wtf/text/StringHash.h>
57 #include <wtf/text/TextPosition.h>
58
59 #if ENABLE(SVG)
60 #include "SVGNames.h"
61 #include "SVGScriptElement.h"
62 #endif
63
64 namespace WebCore {
65
66 ScriptElement::ScriptElement(Element* element, bool parserInserted, bool alreadyStarted)
67     : m_element(element)
68     , m_cachedScript(0)
69     , m_startLineNumber(WTF::OrdinalNumber::beforeFirst())
70     , m_parserInserted(parserInserted)
71     , m_isExternalScript(false)
72     , m_alreadyStarted(alreadyStarted)
73     , m_haveFiredLoad(false)
74     , m_willBeParserExecuted(false)
75     , m_readyToBeParserExecuted(false)
76     , m_willExecuteWhenDocumentFinishedParsing(false)
77     , m_forceAsync(!parserInserted)
78     , m_willExecuteInOrder(false)
79     , m_requestUsesAccessControl(false)
80 {
81     ASSERT(m_element);
82     if (parserInserted && m_element->document().scriptableDocumentParser() && !m_element->document().isInDocumentWrite())
83         m_startLineNumber = m_element->document().scriptableDocumentParser()->lineNumber();
84 }
85
86 ScriptElement::~ScriptElement()
87 {
88     stopLoadRequest();
89 }
90
91 void ScriptElement::insertedInto(ContainerNode& insertionPoint)
92 {
93     if (insertionPoint.inDocument() && !m_parserInserted)
94         prepareScript(); // FIXME: Provide a real starting line number here.
95 }
96
97 void ScriptElement::childrenChanged()
98 {
99     if (!m_parserInserted && m_element->inDocument())
100         prepareScript(); // FIXME: Provide a real starting line number here.
101 }
102
103 void ScriptElement::handleSourceAttribute(const String& sourceUrl)
104 {
105     if (ignoresLoadRequest() || sourceUrl.isEmpty())
106         return;
107
108     prepareScript(); // FIXME: Provide a real starting line number here.
109 }
110
111 void ScriptElement::handleAsyncAttribute()
112 {
113     m_forceAsync = false;
114 }
115
116 // Helper function
117 static bool isLegacySupportedJavaScriptLanguage(const String& language)
118 {
119     // Mozilla 1.8 accepts javascript1.0 - javascript1.7, but WinIE 7 accepts only javascript1.1 - javascript1.3.
120     // Mozilla 1.8 and WinIE 7 both accept javascript and livescript.
121     // WinIE 7 accepts ecmascript and jscript, but Mozilla 1.8 doesn't.
122     // Neither Mozilla 1.8 nor WinIE 7 accept leading or trailing whitespace.
123     // We want to accept all the values that either of these browsers accept, but not other values.
124
125     // FIXME: This function is not HTML5 compliant. These belong in the MIME registry as "text/javascript<version>" entries.
126     typedef HashSet<String, CaseFoldingHash> LanguageSet;
127     DEFINE_STATIC_LOCAL(LanguageSet, languages, ());
128     if (languages.isEmpty()) {
129         languages.add("javascript");
130         languages.add("javascript");
131         languages.add("javascript1.0");
132         languages.add("javascript1.1");
133         languages.add("javascript1.2");
134         languages.add("javascript1.3");
135         languages.add("javascript1.4");
136         languages.add("javascript1.5");
137         languages.add("javascript1.6");
138         languages.add("javascript1.7");
139         languages.add("livescript");
140         languages.add("ecmascript");
141         languages.add("jscript");
142     }
143
144     return languages.contains(language);
145 }
146
147 void ScriptElement::dispatchErrorEvent()
148 {
149     m_element->dispatchEvent(Event::create(eventNames().errorEvent, false, false));
150 }
151
152 bool ScriptElement::isScriptTypeSupported(LegacyTypeSupport supportLegacyTypes) const
153 {
154     // FIXME: isLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used here to maintain backwards compatibility with existing layout tests. The specific violations are:
155     // - Allowing type=javascript. type= should only support MIME types, such as text/javascript.
156     // - Allowing a different set of languages for language= and type=. language= supports Javascript 1.1 and 1.4-1.6, but type= does not.
157
158     String type = typeAttributeValue();
159     String language = languageAttributeValue();
160     if (type.isEmpty() && language.isEmpty())
161         return true; // Assume text/javascript.
162     if (type.isEmpty()) {
163         type = "text/" + language.lower();
164         if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type) || isLegacySupportedJavaScriptLanguage(language))
165             return true;
166     } else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace().lower()) || (supportLegacyTypes == AllowLegacyTypeInTypeAttribute && isLegacySupportedJavaScriptLanguage(type)))
167         return true;
168     return false;
169 }
170
171 // http://dev.w3.org/html5/spec/Overview.html#prepare-a-script
172 bool ScriptElement::prepareScript(const TextPosition& scriptStartPosition, LegacyTypeSupport supportLegacyTypes)
173 {
174     if (m_alreadyStarted)
175         return false;
176
177     bool wasParserInserted;
178     if (m_parserInserted) {
179         wasParserInserted = true;
180         m_parserInserted = false;
181     } else
182         wasParserInserted = false;
183
184     if (wasParserInserted && !asyncAttributeValue())
185         m_forceAsync = true;
186
187     // FIXME: HTML5 spec says we should check that all children are either comments or empty text nodes.
188     if (!hasSourceAttribute() && !m_element->firstChild())
189         return false;
190
191     if (!m_element->inDocument())
192         return false;
193
194     if (!isScriptTypeSupported(supportLegacyTypes))
195         return false;
196
197     if (wasParserInserted) {
198         m_parserInserted = true;
199         m_forceAsync = false;
200     }
201
202     m_alreadyStarted = true;
203
204     // FIXME: If script is parser inserted, verify it's still in the original document.
205     Document& document = m_element->document();
206
207     // FIXME: Eventually we'd like to evaluate scripts which are inserted into a
208     // viewless document but this'll do for now.
209     // See http://bugs.webkit.org/show_bug.cgi?id=5727
210     if (!document.frame())
211         return false;
212
213     if (!document.frame()->script().canExecuteScripts(AboutToExecuteScript))
214         return false;
215
216     if (!isScriptForEventSupported())
217         return false;
218
219     if (!charsetAttributeValue().isEmpty())
220         m_characterEncoding = charsetAttributeValue();
221     else
222         m_characterEncoding = document.charset();
223
224     if (hasSourceAttribute())
225         if (!requestScript(sourceAttributeValue()))
226             return false;
227
228     if (hasSourceAttribute() && deferAttributeValue() && m_parserInserted && !asyncAttributeValue()) {
229         m_willExecuteWhenDocumentFinishedParsing = true;
230         m_willBeParserExecuted = true;
231     } else if (hasSourceAttribute() && m_parserInserted && !asyncAttributeValue())
232         m_willBeParserExecuted = true;
233     else if (!hasSourceAttribute() && m_parserInserted && !document.haveStylesheetsLoaded()) {
234         m_willBeParserExecuted = true;
235         m_readyToBeParserExecuted = true;
236     } else if (hasSourceAttribute() && !asyncAttributeValue() && !m_forceAsync) {
237         m_willExecuteInOrder = true;
238         document.scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::IN_ORDER_EXECUTION);
239         m_cachedScript->addClient(this);
240     } else if (hasSourceAttribute()) {
241         m_element->document().scriptRunner()->queueScriptForExecution(this, m_cachedScript, ScriptRunner::ASYNC_EXECUTION);
242         m_cachedScript->addClient(this);
243     } else {
244         // Reset line numbering for nested writes.
245         TextPosition position = document.isInDocumentWrite() ? TextPosition() : scriptStartPosition;
246         executeScript(ScriptSourceCode(scriptContent(), document.url(), position));
247     }
248
249     return true;
250 }
251
252 bool ScriptElement::requestScript(const String& sourceUrl)
253 {
254     RefPtr<Document> originalDocument = &m_element->document();
255     if (!m_element->dispatchBeforeLoadEvent(sourceUrl))
256         return false;
257     if (!m_element->inDocument() || &m_element->document() != originalDocument)
258         return false;
259     if (!m_element->document().contentSecurityPolicy()->allowScriptNonce(m_element->fastGetAttribute(HTMLNames::nonceAttr), m_element->document().url(), m_startLineNumber, m_element->document().completeURL(sourceUrl)))
260         return false;
261
262     ASSERT(!m_cachedScript);
263     if (!stripLeadingAndTrailingHTMLSpaces(sourceUrl).isEmpty()) {
264         CachedResourceRequest request(ResourceRequest(m_element->document().completeURL(sourceUrl)));
265
266         String crossOriginMode = m_element->fastGetAttribute(HTMLNames::crossoriginAttr);
267         if (!crossOriginMode.isNull()) {
268             m_requestUsesAccessControl = true;
269             StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
270             updateRequestForAccessControl(request.mutableResourceRequest(), m_element->document().securityOrigin(), allowCredentials);
271         }
272         request.setCharset(scriptCharset());
273         request.setInitiator(element());
274
275         m_cachedScript = m_element->document().cachedResourceLoader()->requestScript(request);
276         m_isExternalScript = true;
277     }
278
279     if (m_cachedScript) {
280         return true;
281     }
282
283     dispatchErrorEvent();
284     return false;
285 }
286
287 void ScriptElement::executeScript(const ScriptSourceCode& sourceCode)
288 {
289     ASSERT(m_alreadyStarted);
290
291     if (sourceCode.isEmpty())
292         return;
293
294     if (!m_element->document().contentSecurityPolicy()->allowScriptNonce(m_element->fastGetAttribute(HTMLNames::nonceAttr), m_element->document().url(), m_startLineNumber))
295         return;
296
297     if (!m_isExternalScript && !m_element->document().contentSecurityPolicy()->allowInlineScript(m_element->document().url(), m_startLineNumber))
298         return;
299
300 #if ENABLE(NOSNIFF)
301     if (m_isExternalScript && m_cachedScript && !m_cachedScript->mimeTypeAllowedByNosniff()) {
302         m_element->document().addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, "Refused to execute script from '" + m_cachedScript->url().stringCenterEllipsizedToLength() + "' because its MIME type ('" + m_cachedScript->mimeType() + "') is not executable, and strict MIME type checking is enabled.");
303         return;
304     }
305 #endif
306
307     RefPtr<Document> document = &m_element->document();
308     if (Frame* frame = document->frame()) {
309         IgnoreDestructiveWriteCountIncrementer ignoreDesctructiveWriteCountIncrementer(m_isExternalScript ? document.get() : 0);
310         CurrentScriptIncrementer currentScriptIncrementer(document.get(), m_element);
311
312         // Create a script from the script element node, using the script
313         // block's source and the script block's type.
314         // Note: This is where the script is compiled and actually executed.
315         frame->script().evaluate(sourceCode);
316     }
317 }
318
319 void ScriptElement::stopLoadRequest()
320 {
321     if (m_cachedScript) {
322         if (!m_willBeParserExecuted)
323             m_cachedScript->removeClient(this);
324         m_cachedScript = 0;
325     }
326 }
327
328 void ScriptElement::execute(CachedScript* cachedScript)
329 {
330     ASSERT(!m_willBeParserExecuted);
331     ASSERT(cachedScript);
332     if (cachedScript->errorOccurred())
333         dispatchErrorEvent();
334     else if (!cachedScript->wasCanceled()) {
335         executeScript(ScriptSourceCode(cachedScript));
336         dispatchLoadEvent();
337     }
338     cachedScript->removeClient(this);
339 }
340
341 void ScriptElement::notifyFinished(CachedResource* resource)
342 {
343     ASSERT(!m_willBeParserExecuted);
344
345     // CachedResource possibly invokes this notifyFinished() more than
346     // once because ScriptElement doesn't unsubscribe itself from
347     // CachedResource here and does it in execute() instead.
348     // We use m_cachedScript to check if this function is already called.
349     ASSERT_UNUSED(resource, resource == m_cachedScript);
350     if (!m_cachedScript)
351         return;
352
353     if (m_requestUsesAccessControl
354         && !m_element->document().securityOrigin()->canRequest(m_cachedScript->response().url())
355         && !m_cachedScript->passesAccessControlCheck(m_element->document().securityOrigin())) {
356
357         dispatchErrorEvent();
358         DEFINE_STATIC_LOCAL(String, consoleMessage, (ASCIILiteral("Cross-origin script load denied by Cross-Origin Resource Sharing policy.")));
359         m_element->document().addConsoleMessage(JSMessageSource, ErrorMessageLevel, consoleMessage);
360         return;
361     }
362
363     if (m_willExecuteInOrder)
364         m_element->document().scriptRunner()->notifyScriptReady(this, ScriptRunner::IN_ORDER_EXECUTION);
365     else
366         m_element->document().scriptRunner()->notifyScriptReady(this, ScriptRunner::ASYNC_EXECUTION);
367
368     m_cachedScript = 0;
369 }
370
371 bool ScriptElement::ignoresLoadRequest() const
372 {
373     return m_alreadyStarted || m_isExternalScript || m_parserInserted || !m_element->inDocument();
374 }
375
376 bool ScriptElement::isScriptForEventSupported() const
377 {
378     String eventAttribute = eventAttributeValue();
379     String forAttribute = forAttributeValue();
380     if (!eventAttribute.isEmpty() && !forAttribute.isEmpty()) {
381         forAttribute = forAttribute.stripWhiteSpace();
382         if (!equalIgnoringCase(forAttribute, "window"))
383             return false;
384
385         eventAttribute = eventAttribute.stripWhiteSpace();
386         if (!equalIgnoringCase(eventAttribute, "onload") && !equalIgnoringCase(eventAttribute, "onload()"))
387             return false;
388     }
389     return true;
390 }
391
392 String ScriptElement::scriptContent() const
393 {
394     return TextNodeTraversal::contentsAsString(m_element);
395 }
396
397 ScriptElement* toScriptElementIfPossible(Element* element)
398 {
399     if (isHTMLScriptElement(element))
400         return toHTMLScriptElement(element);
401
402 #if ENABLE(SVG)
403     if (isSVGScriptElement(element))
404         return toSVGScriptElement(element);
405 #endif
406
407     return 0;
408 }
409
410 }