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 "core/dom/ScriptLoader.h"
27 #include "bindings/core/v8/ScriptController.h"
28 #include "bindings/core/v8/ScriptSourceCode.h"
29 #include "core/HTMLNames.h"
30 #include "core/SVGNames.h"
31 #include "core/dom/Document.h"
32 #include "core/dom/IgnoreDestructiveWriteCountIncrementer.h"
33 #include "core/dom/ScriptLoaderClient.h"
34 #include "core/dom/ScriptRunner.h"
35 #include "core/dom/ScriptableDocumentParser.h"
36 #include "core/dom/Text.h"
37 #include "core/events/Event.h"
38 #include "core/fetch/AccessControlStatus.h"
39 #include "core/fetch/FetchRequest.h"
40 #include "core/fetch/ResourceFetcher.h"
41 #include "core/fetch/ScriptResource.h"
42 #include "core/frame/LocalFrame.h"
43 #include "core/frame/SubresourceIntegrity.h"
44 #include "core/frame/UseCounter.h"
45 #include "core/frame/csp/ContentSecurityPolicy.h"
46 #include "core/html/HTMLScriptElement.h"
47 #include "core/html/imports/HTMLImport.h"
48 #include "core/html/parser/HTMLParserIdioms.h"
49 #include "core/inspector/ConsoleMessage.h"
50 #include "core/svg/SVGScriptElement.h"
51 #include "platform/MIMETypeRegistry.h"
52 #include "platform/weborigin/SecurityOrigin.h"
53 #include "wtf/StdLibExtras.h"
54 #include "wtf/text/StringBuilder.h"
55 #include "wtf/text/StringHash.h"
59 ScriptLoader::ScriptLoader(Element
* element
, bool parserInserted
, bool alreadyStarted
)
62 , m_startLineNumber(WTF::OrdinalNumber::beforeFirst())
63 , m_parserInserted(parserInserted
)
64 , m_isExternalScript(false)
65 , m_alreadyStarted(alreadyStarted
)
66 , m_haveFiredLoad(false)
67 , m_willBeParserExecuted(false)
68 , m_readyToBeParserExecuted(false)
69 , m_willExecuteWhenDocumentFinishedParsing(false)
70 , m_forceAsync(!parserInserted
)
71 , m_willExecuteInOrder(false)
74 if (parserInserted
&& element
->document().scriptableDocumentParser() && !element
->document().isInDocumentWrite())
75 m_startLineNumber
= element
->document().scriptableDocumentParser()->lineNumber();
78 ScriptLoader::~ScriptLoader()
80 m_pendingScript
.stopWatchingForLoad(this);
83 DEFINE_TRACE(ScriptLoader
)
85 visitor
->trace(m_element
);
86 visitor
->trace(m_pendingScript
);
89 void ScriptLoader::didNotifySubtreeInsertionsToDocument()
91 if (!m_parserInserted
)
92 prepareScript(); // FIXME: Provide a real starting line number here.
95 void ScriptLoader::childrenChanged()
97 if (!m_parserInserted
&& m_element
->inDocument())
98 prepareScript(); // FIXME: Provide a real starting line number here.
101 void ScriptLoader::handleSourceAttribute(const String
& sourceUrl
)
103 if (ignoresLoadRequest() || sourceUrl
.isEmpty())
106 prepareScript(); // FIXME: Provide a real starting line number here.
109 void ScriptLoader::handleAsyncAttribute()
111 m_forceAsync
= false;
114 void ScriptLoader::detach()
116 m_pendingScript
.stopWatchingForLoad(this);
117 m_pendingScript
.releaseElementAndClear();
121 static bool isLegacySupportedJavaScriptLanguage(const String
& language
)
123 // Mozilla 1.8 accepts javascript1.0 - javascript1.7, but WinIE 7 accepts only javascript1.1 - javascript1.3.
124 // Mozilla 1.8 and WinIE 7 both accept javascript and livescript.
125 // WinIE 7 accepts ecmascript and jscript, but Mozilla 1.8 doesn't.
126 // Neither Mozilla 1.8 nor WinIE 7 accept leading or trailing whitespace.
127 // We want to accept all the values that either of these browsers accept, but not other values.
129 // FIXME: This function is not HTML5 compliant. These belong in the MIME registry as "text/javascript<version>" entries.
130 typedef HashSet
<String
, CaseFoldingHash
> LanguageSet
;
131 DEFINE_STATIC_LOCAL(LanguageSet
, languages
, ());
132 if (languages
.isEmpty()) {
133 languages
.add("javascript");
134 languages
.add("javascript1.0");
135 languages
.add("javascript1.1");
136 languages
.add("javascript1.2");
137 languages
.add("javascript1.3");
138 languages
.add("javascript1.4");
139 languages
.add("javascript1.5");
140 languages
.add("javascript1.6");
141 languages
.add("javascript1.7");
142 languages
.add("livescript");
143 languages
.add("ecmascript");
144 languages
.add("jscript");
147 return languages
.contains(language
);
150 void ScriptLoader::dispatchErrorEvent()
152 m_element
->dispatchEvent(Event::create(EventTypeNames::error
));
155 void ScriptLoader::dispatchLoadEvent()
157 if (ScriptLoaderClient
* client
= this->client())
158 client
->dispatchLoadEvent();
159 setHaveFiredLoadEvent(true);
162 bool ScriptLoader::isScriptTypeSupported(LegacyTypeSupport supportLegacyTypes
) const
164 // FIXME: isLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used here to maintain backwards compatibility with existing layout tests. The specific violations are:
165 // - Allowing type=javascript. type= should only support MIME types, such as text/javascript.
166 // - Allowing a different set of languages for language= and type=. language= supports Javascript 1.1 and 1.4-1.6, but type= does not.
168 String type
= client()->typeAttributeValue();
169 String language
= client()->languageAttributeValue();
170 if (type
.isEmpty() && language
.isEmpty())
171 return true; // Assume text/javascript.
172 if (type
.isEmpty()) {
173 type
= "text/" + language
.lower();
174 if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type
) || isLegacySupportedJavaScriptLanguage(language
))
176 } else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type
.stripWhiteSpace()) || (supportLegacyTypes
== AllowLegacyTypeInTypeAttribute
&& isLegacySupportedJavaScriptLanguage(type
))) {
183 // http://dev.w3.org/html5/spec/Overview.html#prepare-a-script
184 bool ScriptLoader::prepareScript(const TextPosition
& scriptStartPosition
, LegacyTypeSupport supportLegacyTypes
)
186 if (m_alreadyStarted
)
189 ScriptLoaderClient
* client
= this->client();
191 bool wasParserInserted
;
192 if (m_parserInserted
) {
193 wasParserInserted
= true;
194 m_parserInserted
= false;
196 wasParserInserted
= false;
199 if (wasParserInserted
&& !client
->asyncAttributeValue())
202 // FIXME: HTML5 spec says we should check that all children are either comments or empty text nodes.
203 if (!client
->hasSourceAttribute() && !m_element
->hasChildren())
206 if (!m_element
->inDocument())
209 if (!isScriptTypeSupported(supportLegacyTypes
))
212 if (wasParserInserted
) {
213 m_parserInserted
= true;
214 m_forceAsync
= false;
217 m_alreadyStarted
= true;
219 // FIXME: If script is parser inserted, verify it's still in the original document.
220 Document
& elementDocument
= m_element
->document();
221 Document
* contextDocument
= elementDocument
.contextDocument().get();
223 if (!contextDocument
|| !contextDocument
->allowExecutingScripts(m_element
))
226 if (!isScriptForEventSupported())
229 if (!client
->charsetAttributeValue().isEmpty())
230 m_characterEncoding
= client
->charsetAttributeValue();
232 m_characterEncoding
= elementDocument
.charset();
234 if (client
->hasSourceAttribute()) {
235 FetchRequest::DeferOption defer
= FetchRequest::NoDefer
;
236 if (!m_parserInserted
|| client
->asyncAttributeValue() || client
->deferAttributeValue())
237 defer
= FetchRequest::LazyLoad
;
238 if (!fetchScript(client
->sourceAttributeValue(), defer
))
242 if (client
->hasSourceAttribute() && client
->deferAttributeValue() && m_parserInserted
&& !client
->asyncAttributeValue()) {
243 m_willExecuteWhenDocumentFinishedParsing
= true;
244 m_willBeParserExecuted
= true;
245 } else if (client
->hasSourceAttribute() && m_parserInserted
&& !client
->asyncAttributeValue()) {
246 m_willBeParserExecuted
= true;
247 } else if (!client
->hasSourceAttribute() && m_parserInserted
&& !elementDocument
.isRenderingReady()) {
248 m_willBeParserExecuted
= true;
249 m_readyToBeParserExecuted
= true;
250 } else if (client
->hasSourceAttribute() && !client
->asyncAttributeValue() && !m_forceAsync
) {
251 m_willExecuteInOrder
= true;
252 m_pendingScript
= PendingScript(m_element
, m_resource
.get());
253 contextDocument
->scriptRunner()->queueScriptForExecution(this, ScriptRunner::IN_ORDER_EXECUTION
);
254 // Note that watchForLoad can immediately call notifyFinished.
255 m_pendingScript
.watchForLoad(this);
256 } else if (client
->hasSourceAttribute()) {
257 m_pendingScript
= PendingScript(m_element
, m_resource
.get());
258 LocalFrame
* frame
= m_element
->document().frame();
260 ScriptState
* scriptState
= ScriptState::forMainWorld(frame
);
261 if (scriptState
->contextIsValid())
262 ScriptStreamer::startStreaming(m_pendingScript
, PendingScript::Async
, frame
->settings(), scriptState
);
264 contextDocument
->scriptRunner()->queueScriptForExecution(this, ScriptRunner::ASYNC_EXECUTION
);
265 // Note that watchForLoad can immediately call notifyFinished.
266 m_pendingScript
.watchForLoad(this);
268 // Reset line numbering for nested writes.
269 TextPosition position
= elementDocument
.isInDocumentWrite() ? TextPosition() : scriptStartPosition
;
270 KURL scriptURL
= (!elementDocument
.isInDocumentWrite() && m_parserInserted
) ? elementDocument
.url() : KURL();
271 if (!executeScript(ScriptSourceCode(scriptContent(), scriptURL
, position
))) {
272 dispatchErrorEvent();
280 bool ScriptLoader::fetchScript(const String
& sourceUrl
, FetchRequest::DeferOption defer
)
284 RefPtrWillBeRawPtr
<Document
> elementDocument(m_element
->document());
285 if (!m_element
->inDocument() || m_element
->document() != elementDocument
)
289 if (!stripLeadingAndTrailingHTMLSpaces(sourceUrl
).isEmpty()) {
290 FetchRequest
request(ResourceRequest(elementDocument
->completeURL(sourceUrl
)), m_element
->localName());
292 AtomicString crossOriginMode
= m_element
->fastGetAttribute(HTMLNames::crossoriginAttr
);
293 if (!crossOriginMode
.isNull())
294 request
.setCrossOriginAccessControl(elementDocument
->securityOrigin(), crossOriginMode
);
295 request
.setCharset(scriptCharset());
297 bool scriptPassesCSP
= elementDocument
->contentSecurityPolicy()->allowScriptWithNonce(m_element
->fastGetAttribute(HTMLNames::nonceAttr
));
299 request
.setContentSecurityCheck(DoNotCheckContentSecurityPolicy
);
300 request
.setDefer(defer
);
302 m_resource
= ScriptResource::fetch(request
, elementDocument
->fetcher());
303 m_isExternalScript
= true;
309 dispatchErrorEvent();
313 bool isHTMLScriptLoader(Element
* element
)
316 return isHTMLScriptElement(*element
);
319 bool isSVGScriptLoader(Element
* element
)
322 return isSVGScriptElement(*element
);
325 bool ScriptLoader::executeScript(const ScriptSourceCode
& sourceCode
, double* compilationFinishTime
)
327 ASSERT(m_alreadyStarted
);
329 if (sourceCode
.isEmpty())
332 RefPtrWillBeRawPtr
<Document
> elementDocument(m_element
->document());
333 RefPtrWillBeRawPtr
<Document
> contextDocument
= elementDocument
->contextDocument().get();
334 if (!contextDocument
)
337 LocalFrame
* frame
= contextDocument
->frame();
339 const ContentSecurityPolicy
* csp
= elementDocument
->contentSecurityPolicy();
340 bool shouldBypassMainWorldCSP
= (frame
&& frame
->script().shouldBypassMainWorldCSP())
341 || csp
->allowScriptWithNonce(m_element
->fastGetAttribute(HTMLNames::nonceAttr
))
342 || csp
->allowScriptWithHash(sourceCode
.source());
344 if (!m_isExternalScript
&& (!shouldBypassMainWorldCSP
&& !csp
->allowInlineScript(elementDocument
->url(), m_startLineNumber
, sourceCode
.source()))) {
348 if (m_isExternalScript
) {
349 ScriptResource
* resource
= m_resource
? m_resource
.get() : sourceCode
.resource();
350 if (resource
&& !resource
->mimeTypeAllowedByNosniff()) {
351 contextDocument
->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource
, ErrorMessageLevel
, "Refused to execute script from '" + resource
->url().elidedString() + "' because its MIME type ('" + resource
->mimeType() + "') is not executable, and strict MIME type checking is enabled."));
355 if (resource
&& resource
->mimeType().lower().startsWith("image/")) {
356 contextDocument
->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource
, ErrorMessageLevel
, "Refused to execute script from '" + resource
->url().elidedString() + "' because its MIME type ('" + resource
->mimeType() + "') is not executable."));
357 UseCounter::count(frame
, UseCounter::BlockedSniffingImageToScript
);
362 // FIXME: Can this be moved earlier in the function?
363 // Why are we ever attempting to execute scripts without a frame?
367 AccessControlStatus accessControlStatus
= NotSharableCrossOrigin
;
368 if (!m_isExternalScript
) {
369 accessControlStatus
= SharableCrossOrigin
;
370 } else if (sourceCode
.resource()) {
371 if (sourceCode
.resource()->response().wasFetchedViaServiceWorker()) {
372 if (sourceCode
.resource()->response().serviceWorkerResponseType() == WebServiceWorkerResponseTypeOpaque
)
373 accessControlStatus
= OpaqueResource
;
375 accessControlStatus
= SharableCrossOrigin
;
376 } else if (sourceCode
.resource()->passesAccessControlCheck(m_element
->document().securityOrigin())) {
377 accessControlStatus
= SharableCrossOrigin
;
381 // The following SRI checks need to be here because, unfortunately, fetches
382 // are not done purely according to the Fetch spec. In particular,
383 // different requests for the same resource do not have different
384 // responses; the memory cache can (and will) return the exact same
385 // Resource object. For different requests, the same Resource object will
386 // be returned and will not be associated with the particular request.
387 // Therefore, when the body of the response comes in, there's no way to
388 // validate the integrity of the Resource object against a particular
389 // request (since there may be several pending requests all tied to the
390 // identical object, and the actual requests are not stored).
392 // In order to simulate the correct behavior, Blink explicitly does the SRI
393 // checks at execution here (similar to the AccessControlStatus check done
394 // above), while having proper Fetch checks in the fetch module for use in
395 // the fetch JavaScript API. In a future world where the ResourceFetcher
396 // uses the Fetch algorithm, this should be fixed by having separate
397 // Response objects (perhaps attached to identical Resource objects) per
398 // request. See https://crbug.com/500701 for more information.
399 if (m_isExternalScript
) {
400 const KURL resourceUrl
= sourceCode
.resource()->resourceRequest().url();
401 if (!SubresourceIntegrity::CheckSubresourceIntegrity(*m_element
, sourceCode
.source(), sourceCode
.resource()->url(), *sourceCode
.resource())) {
406 const bool isImportedScript
= contextDocument
!= elementDocument
;
407 // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block step 2.3
408 // with additional support for HTML imports.
409 IgnoreDestructiveWriteCountIncrementer
ignoreDestructiveWriteCountIncrementer(m_isExternalScript
|| isImportedScript
? contextDocument
.get() : 0);
411 if (isHTMLScriptLoader(m_element
))
412 contextDocument
->pushCurrentScript(toHTMLScriptElement(m_element
));
414 // Create a script from the script element node, using the script
415 // block's source and the script block's type.
416 // Note: This is where the script is compiled and actually executed.
417 frame
->script().executeScriptInMainWorld(sourceCode
, accessControlStatus
, compilationFinishTime
);
419 if (isHTMLScriptLoader(m_element
)) {
420 ASSERT(contextDocument
->currentScript() == m_element
);
421 contextDocument
->popCurrentScript();
427 void ScriptLoader::execute()
429 ASSERT(!m_willBeParserExecuted
);
430 ASSERT(m_pendingScript
.resource());
431 bool errorOccurred
= false;
432 ScriptSourceCode source
= m_pendingScript
.getSource(KURL(), errorOccurred
);
433 RefPtrWillBeRawPtr
<Element
> element
= m_pendingScript
.releaseElementAndClear();
434 ALLOW_UNUSED_LOCAL(element
);
436 dispatchErrorEvent();
437 } else if (!m_resource
->wasCanceled()) {
438 if (executeScript(source
))
441 dispatchErrorEvent();
446 void ScriptLoader::notifyFinished(Resource
* resource
)
448 ASSERT(!m_willBeParserExecuted
);
450 RefPtrWillBeRawPtr
<Document
> elementDocument(m_element
->document());
451 RefPtrWillBeRawPtr
<Document
> contextDocument
= elementDocument
->contextDocument().get();
452 if (!contextDocument
)
455 ASSERT_UNUSED(resource
, resource
== m_resource
);
457 if (m_resource
->errorOccurred()) {
458 dispatchErrorEvent();
459 // dispatchErrorEvent might move the HTMLScriptElement to a new
460 // document. In that case, we must notify the ScriptRunner of the new
461 // document, not the ScriptRunner of the old docuemnt.
462 contextDocument
= m_element
->document().contextDocument().get();
463 if (!contextDocument
)
465 contextDocument
->scriptRunner()->notifyScriptLoadError(this, m_willExecuteInOrder
? ScriptRunner::IN_ORDER_EXECUTION
: ScriptRunner::ASYNC_EXECUTION
);
468 if (m_willExecuteInOrder
)
469 contextDocument
->scriptRunner()->notifyScriptReady(this, ScriptRunner::IN_ORDER_EXECUTION
);
471 contextDocument
->scriptRunner()->notifyScriptReady(this, ScriptRunner::ASYNC_EXECUTION
);
473 m_pendingScript
.stopWatchingForLoad(this);
476 bool ScriptLoader::ignoresLoadRequest() const
478 return m_alreadyStarted
|| m_isExternalScript
|| m_parserInserted
|| !element() || !element()->inDocument();
481 bool ScriptLoader::isScriptForEventSupported() const
483 String eventAttribute
= client()->eventAttributeValue();
484 String forAttribute
= client()->forAttributeValue();
485 if (eventAttribute
.isNull() || forAttribute
.isNull())
488 forAttribute
= forAttribute
.stripWhiteSpace();
489 if (!equalIgnoringCase(forAttribute
, "window"))
491 eventAttribute
= eventAttribute
.stripWhiteSpace();
492 return equalIgnoringCase(eventAttribute
, "onload") || equalIgnoringCase(eventAttribute
, "onload()");
495 String
ScriptLoader::scriptContent() const
497 return m_element
->textFromChildren();
500 ScriptLoaderClient
* ScriptLoader::client() const
502 if (isHTMLScriptLoader(m_element
))
503 return toHTMLScriptElement(m_element
);
505 if (isSVGScriptLoader(m_element
))
506 return toSVGScriptElement(m_element
);
508 ASSERT_NOT_REACHED();
512 ScriptLoader
* toScriptLoaderIfPossible(Element
* element
)
514 if (isHTMLScriptLoader(element
))
515 return toHTMLScriptElement(element
)->loader();
517 if (isSVGScriptLoader(element
))
518 return toSVGScriptElement(element
)->loader();