Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / core / dom / ScriptLoader.cpp
blob7a16bd0582d90bc03939eba018017e0fe259262c
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>
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.
24 #include "config.h"
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"
57 namespace blink {
59 ScriptLoader::ScriptLoader(Element* element, bool parserInserted, bool alreadyStarted)
60 : m_element(element)
61 , m_resource(0)
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)
73 ASSERT(m_element);
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())
104 return;
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();
120 // Helper function
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))
175 return true;
176 } else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace()) || (supportLegacyTypes == AllowLegacyTypeInTypeAttribute && isLegacySupportedJavaScriptLanguage(type))) {
177 return true;
180 return false;
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)
187 return false;
189 ScriptLoaderClient* client = this->client();
191 bool wasParserInserted;
192 if (m_parserInserted) {
193 wasParserInserted = true;
194 m_parserInserted = false;
195 } else {
196 wasParserInserted = false;
199 if (wasParserInserted && !client->asyncAttributeValue())
200 m_forceAsync = true;
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())
204 return false;
206 if (!m_element->inDocument())
207 return false;
209 if (!isScriptTypeSupported(supportLegacyTypes))
210 return false;
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))
224 return false;
226 if (!isScriptForEventSupported())
227 return false;
229 if (!client->charsetAttributeValue().isEmpty())
230 m_characterEncoding = client->charsetAttributeValue();
231 else
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))
239 return false;
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();
259 if (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);
267 } else {
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();
273 return false;
277 return true;
280 bool ScriptLoader::fetchScript(const String& sourceUrl, FetchRequest::DeferOption defer)
282 ASSERT(m_element);
284 RefPtrWillBeRawPtr<Document> elementDocument(m_element->document());
285 if (!m_element->inDocument() || m_element->document() != elementDocument)
286 return false;
288 ASSERT(!m_resource);
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));
298 if (scriptPassesCSP)
299 request.setContentSecurityCheck(DoNotCheckContentSecurityPolicy);
300 request.setDefer(defer);
302 m_resource = ScriptResource::fetch(request, elementDocument->fetcher());
303 m_isExternalScript = true;
306 if (m_resource)
307 return true;
309 dispatchErrorEvent();
310 return false;
313 bool isHTMLScriptLoader(Element* element)
315 ASSERT(element);
316 return isHTMLScriptElement(*element);
319 bool isSVGScriptLoader(Element* element)
321 ASSERT(element);
322 return isSVGScriptElement(*element);
325 bool ScriptLoader::executeScript(const ScriptSourceCode& sourceCode, double* compilationFinishTime)
327 ASSERT(m_alreadyStarted);
329 if (sourceCode.isEmpty())
330 return true;
332 RefPtrWillBeRawPtr<Document> elementDocument(m_element->document());
333 RefPtrWillBeRawPtr<Document> contextDocument = elementDocument->contextDocument().get();
334 if (!contextDocument)
335 return true;
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()))) {
345 return false;
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."));
352 return false;
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);
358 return false;
362 // FIXME: Can this be moved earlier in the function?
363 // Why are we ever attempting to execute scripts without a frame?
364 if (!frame)
365 return true;
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;
374 else
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())) {
402 return false;
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();
424 return true;
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);
435 if (errorOccurred) {
436 dispatchErrorEvent();
437 } else if (!m_resource->wasCanceled()) {
438 if (executeScript(source))
439 dispatchLoadEvent();
440 else
441 dispatchErrorEvent();
443 m_resource = 0;
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)
453 return;
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)
464 return;
465 contextDocument->scriptRunner()->notifyScriptLoadError(this, m_willExecuteInOrder ? ScriptRunner::IN_ORDER_EXECUTION : ScriptRunner::ASYNC_EXECUTION);
466 return;
468 if (m_willExecuteInOrder)
469 contextDocument->scriptRunner()->notifyScriptReady(this, ScriptRunner::IN_ORDER_EXECUTION);
470 else
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())
486 return true;
488 forAttribute = forAttribute.stripWhiteSpace();
489 if (!equalIgnoringCase(forAttribute, "window"))
490 return false;
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();
509 return 0;
512 ScriptLoader* toScriptLoaderIfPossible(Element* element)
514 if (isHTMLScriptLoader(element))
515 return toHTMLScriptElement(element)->loader();
517 if (isSVGScriptLoader(element))
518 return toSVGScriptElement(element)->loader();
520 return 0;
523 } // namespace blink