Improve use of NeverDestroyed
[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-2017 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 "CachedResourceLoader.h"
28 #include "CachedResourceRequest.h"
29 #include "CachedScript.h"
30 #include "ContentSecurityPolicy.h"
31 #include "CrossOriginAccessControl.h"
32 #include "CurrentScriptIncrementer.h"
33 #include "Event.h"
34 #include "EventNames.h"
35 #include "Frame.h"
36 #include "FrameLoader.h"
37 #include "HTMLNames.h"
38 #include "HTMLParserIdioms.h"
39 #include "IgnoreDestructiveWriteCountIncrementer.h"
40 #include "InlineClassicScript.h"
41 #include "LoadableClassicScript.h"
42 #include "LoadableModuleScript.h"
43 #include "MIMETypeRegistry.h"
44 #include "NoEventDispatchAssertion.h"
45 #include "PendingScript.h"
46 #include "SVGScriptElement.h"
47 #include "ScriptController.h"
48 #include "ScriptRunner.h"
49 #include "ScriptSourceCode.h"
50 #include "ScriptableDocumentParser.h"
51 #include "Settings.h"
52 #include "TextNodeTraversal.h"
53 #include <wtf/StdLibExtras.h>
54 #include <wtf/text/StringBuilder.h>
55 #include <wtf/text/StringHash.h>
56
57 namespace WebCore {
58
59 ScriptElement::ScriptElement(Element& element, bool parserInserted, bool alreadyStarted)
60     : m_element(element)
61     , m_startLineNumber(WTF::OrdinalNumber::beforeFirst())
62     , m_parserInserted(parserInserted)
63     , m_isExternalScript(false)
64     , m_alreadyStarted(alreadyStarted)
65     , m_haveFiredLoad(false)
66     , m_willBeParserExecuted(false)
67     , m_readyToBeParserExecuted(false)
68     , m_willExecuteWhenDocumentFinishedParsing(false)
69     , m_forceAsync(!parserInserted)
70     , m_willExecuteInOrder(false)
71     , m_isModuleScript(false)
72 {
73     if (parserInserted && m_element.document().scriptableDocumentParser() && !m_element.document().isInDocumentWrite())
74         m_startLineNumber = m_element.document().scriptableDocumentParser()->textPosition().m_line;
75 }
76
77 bool ScriptElement::shouldCallFinishedInsertingSubtree(ContainerNode& insertionPoint)
78 {
79     return insertionPoint.isConnected() && !m_parserInserted;
80 }
81
82 void ScriptElement::finishedInsertingSubtree()
83 {
84     ASSERT(!m_parserInserted);
85     prepareScript(); // FIXME: Provide a real starting line number here.
86 }
87
88 void ScriptElement::childrenChanged(const ContainerNode::ChildChange& childChange)
89 {
90     if (!m_parserInserted && childChange.isInsertion() && m_element.isConnected())
91         prepareScript(); // FIXME: Provide a real starting line number here.
92 }
93
94 void ScriptElement::handleSourceAttribute(const String& sourceURL)
95 {
96     if (ignoresLoadRequest() || sourceURL.isEmpty())
97         return;
98
99     prepareScript(); // FIXME: Provide a real starting line number here.
100 }
101
102 void ScriptElement::handleAsyncAttribute()
103 {
104     m_forceAsync = false;
105 }
106
107 static bool isLegacySupportedJavaScriptLanguage(const String& language)
108 {
109     static const auto languages = makeNeverDestroyed(HashSet<String, ASCIICaseInsensitiveHash> {
110         "javascript",
111         "javascript1.0",
112         "javascript1.1",
113         "javascript1.2",
114         "javascript1.3",
115         "javascript1.4",
116         "javascript1.5",
117         "javascript1.6",
118         "javascript1.7",
119         "livescript",
120         "ecmascript",
121         "jscript",
122     });
123     return languages.get().contains(language);
124 }
125
126 void ScriptElement::dispatchErrorEvent()
127 {
128     m_element.dispatchEvent(Event::create(eventNames().errorEvent, false, false));
129 }
130
131 std::optional<ScriptElement::ScriptType> ScriptElement::determineScriptType(LegacyTypeSupport supportLegacyTypes) const
132 {
133     // FIXME: isLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used here to maintain backwards compatibility with existing layout tests. The specific violations are:
134     // - Allowing type=javascript. type= should only support MIME types, such as text/javascript.
135     // - Allowing a different set of languages for language= and type=. language= supports Javascript 1.1 and 1.4-1.6, but type= does not.
136     String type = typeAttributeValue();
137     String language = languageAttributeValue();
138     if (type.isEmpty()) {
139         if (language.isEmpty())
140             return ScriptType::Classic; // Assume text/javascript.
141         if (MIMETypeRegistry::isSupportedJavaScriptMIMEType("text/" + language))
142             return ScriptType::Classic;
143         if (isLegacySupportedJavaScriptLanguage(language))
144             return ScriptType::Classic;
145         return std::nullopt;
146     }
147     if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.stripWhiteSpace()))
148         return ScriptType::Classic;
149     if (supportLegacyTypes == AllowLegacyTypeInTypeAttribute && isLegacySupportedJavaScriptLanguage(type))
150         return ScriptType::Classic;
151
152     // FIXME: XHTML spec defines "defer" attribute. But WebKit does not implement it for a long time.
153     // And module tag also uses defer attribute semantics. We disable script type="module" for non HTML document.
154     // Once "defer" is implemented, we can reconsider enabling modules in XHTML.
155     // https://bugs.webkit.org/show_bug.cgi?id=123387
156     if (!m_element.document().isHTMLDocument())
157         return std::nullopt;
158
159     // https://html.spec.whatwg.org/multipage/scripting.html#attr-script-type
160     // Setting the attribute to an ASCII case-insensitive match for the string "module" means that the script is a module script.
161     if (equalLettersIgnoringASCIICase(type, "module"))
162         return ScriptType::Module;
163     return std::nullopt;
164 }
165
166 // http://dev.w3.org/html5/spec/Overview.html#prepare-a-script
167 bool ScriptElement::prepareScript(const TextPosition& scriptStartPosition, LegacyTypeSupport supportLegacyTypes)
168 {
169     if (m_alreadyStarted)
170         return false;
171
172     bool wasParserInserted;
173     if (m_parserInserted) {
174         wasParserInserted = true;
175         m_parserInserted = false;
176     } else
177         wasParserInserted = false;
178
179     if (wasParserInserted && !hasAsyncAttribute())
180         m_forceAsync = true;
181
182     // FIXME: HTML5 spec says we should check that all children are either comments or empty text nodes.
183     if (!hasSourceAttribute() && !m_element.firstChild())
184         return false;
185
186     if (!m_element.isConnected())
187         return false;
188
189     ScriptType scriptType = ScriptType::Classic;
190     if (std::optional<ScriptType> result = determineScriptType(supportLegacyTypes))
191         scriptType = result.value();
192     else
193         return false;
194     m_isModuleScript = scriptType == ScriptType::Module;
195
196     if (wasParserInserted) {
197         m_parserInserted = true;
198         m_forceAsync = false;
199     }
200
201     m_alreadyStarted = true;
202
203     // FIXME: If script is parser inserted, verify it's still in the original document.
204     Document& document = m_element.document();
205
206     // FIXME: Eventually we'd like to evaluate scripts which are inserted into a
207     // viewless document but this'll do for now.
208     // See http://bugs.webkit.org/show_bug.cgi?id=5727
209     if (!document.frame())
210         return false;
211
212     if (scriptType == ScriptType::Classic && hasNoModuleAttribute())
213         return false;
214
215     if (!document.frame()->script().canExecuteScripts(AboutToExecuteScript))
216         return false;
217
218     if (scriptType == ScriptType::Classic && !isScriptForEventSupported())
219         return false;
220
221     // According to the spec, the module tag ignores the "charset" attribute as the same to the worker's
222     // importScript. But WebKit supports the "charset" for importScript intentionally. So to be consistent,
223     // even for the module tags, we handle the "charset" attribute.
224     if (!charsetAttributeValue().isEmpty())
225         m_characterEncoding = charsetAttributeValue();
226     else
227         m_characterEncoding = document.charset();
228
229     if (scriptType == ScriptType::Classic) {
230         if (hasSourceAttribute()) {
231             if (!requestClassicScript(sourceAttributeValue()))
232                 return false;
233         }
234     } else {
235         ASSERT(scriptType == ScriptType::Module);
236         if (!requestModuleScript(scriptStartPosition))
237             return false;
238     }
239
240     // All the inlined module script is handled by requestModuleScript. It produces LoadableModuleScript and inlined module script
241     // is handled as the same to the external module script.
242
243     bool isClassicExternalScript = scriptType == ScriptType::Classic && hasSourceAttribute();
244     bool isParserInsertedDeferredScript = ((isClassicExternalScript && hasDeferAttribute()) || scriptType == ScriptType::Module)
245         && m_parserInserted && !hasAsyncAttribute();
246     if (isParserInsertedDeferredScript) {
247         m_willExecuteWhenDocumentFinishedParsing = true;
248         m_willBeParserExecuted = true;
249     } else if (isClassicExternalScript && m_parserInserted && !hasAsyncAttribute()) {
250         ASSERT(scriptType == ScriptType::Classic);
251         m_willBeParserExecuted = true;
252     } else if ((isClassicExternalScript || scriptType == ScriptType::Module) && !hasAsyncAttribute() && !m_forceAsync) {
253         m_willExecuteInOrder = true;
254         ASSERT(m_loadableScript);
255         document.scriptRunner()->queueScriptForExecution(*this, *m_loadableScript, ScriptRunner::IN_ORDER_EXECUTION);
256     } else if (hasSourceAttribute() || scriptType == ScriptType::Module) {
257         ASSERT(m_loadableScript);
258         ASSERT(hasAsyncAttribute() || m_forceAsync);
259         document.scriptRunner()->queueScriptForExecution(*this, *m_loadableScript, ScriptRunner::ASYNC_EXECUTION);
260     } else if (!hasSourceAttribute() && m_parserInserted && !document.haveStylesheetsLoaded()) {
261         ASSERT(scriptType == ScriptType::Classic);
262         m_willBeParserExecuted = true;
263         m_readyToBeParserExecuted = true;
264     } else {
265         ASSERT(scriptType == ScriptType::Classic);
266         TextPosition position = document.isInDocumentWrite() ? TextPosition() : scriptStartPosition;
267         executeClassicScript(ScriptSourceCode(scriptContent(), document.url(), position, JSC::SourceProviderSourceType::Program, InlineClassicScript::create(*this)));
268     }
269
270     return true;
271 }
272
273 bool ScriptElement::requestClassicScript(const String& sourceURL)
274 {
275     Ref<Document> originalDocument(m_element.document());
276     if (!m_element.dispatchBeforeLoadEvent(sourceURL))
277         return false;
278     bool didEventListenerDisconnectThisElement = !m_element.isConnected() || &m_element.document() != originalDocument.ptr();
279     if (didEventListenerDisconnectThisElement)
280         return false;
281
282     ASSERT(!m_loadableScript);
283     if (!stripLeadingAndTrailingHTMLSpaces(sourceURL).isEmpty()) {
284         auto script = LoadableClassicScript::create(
285             m_element.attributeWithoutSynchronization(HTMLNames::nonceAttr),
286             m_element.document().settings().subresourceIntegrityEnabled() ? m_element.attributeWithoutSynchronization(HTMLNames::integrityAttr).string() : emptyString(),
287             m_element.attributeWithoutSynchronization(HTMLNames::crossoriginAttr),
288             scriptCharset(),
289             m_element.localName(),
290             m_element.isInUserAgentShadowTree());
291         if (script->load(m_element.document(), m_element.document().completeURL(sourceURL))) {
292             m_loadableScript = WTFMove(script);
293             m_isExternalScript = true;
294         }
295     }
296
297     if (m_loadableScript)
298         return true;
299
300     callOnMainThread([this, element = Ref<Element>(m_element)] {
301         dispatchErrorEvent();
302     });
303     return false;
304 }
305
306 bool ScriptElement::requestModuleScript(const TextPosition& scriptStartPosition)
307 {
308     String nonce = m_element.attributeWithoutSynchronization(HTMLNames::nonceAttr);
309     String crossOriginMode = m_element.attributeWithoutSynchronization(HTMLNames::crossoriginAttr);
310     if (crossOriginMode.isNull())
311         crossOriginMode = ASCIILiteral("omit");
312
313     if (hasSourceAttribute()) {
314         String sourceURL = sourceAttributeValue();
315         Ref<Document> originalDocument(m_element.document());
316         if (!m_element.dispatchBeforeLoadEvent(sourceURL))
317             return false;
318
319         bool didEventListenerDisconnectThisElement = !m_element.isConnected() || &m_element.document() != originalDocument.ptr();
320         if (didEventListenerDisconnectThisElement)
321             return false;
322
323         if (stripLeadingAndTrailingHTMLSpaces(sourceURL).isEmpty()) {
324             dispatchErrorEvent();
325             return false;
326         }
327
328         auto moduleScriptRootURL = m_element.document().completeURL(sourceURL);
329         if (!moduleScriptRootURL.isValid()) {
330             dispatchErrorEvent();
331             return false;
332         }
333
334         m_isExternalScript = true;
335         auto script = LoadableModuleScript::create(nonce, crossOriginMode, scriptCharset(), m_element.localName(), m_element.isInUserAgentShadowTree());
336         script->load(m_element.document(), moduleScriptRootURL);
337         m_loadableScript = WTFMove(script);
338         return true;
339     }
340
341     auto script = LoadableModuleScript::create(nonce, crossOriginMode, scriptCharset(), m_element.localName(), m_element.isInUserAgentShadowTree());
342
343     TextPosition position = m_element.document().isInDocumentWrite() ? TextPosition() : scriptStartPosition;
344     ScriptSourceCode sourceCode(scriptContent(), m_element.document().url(), position, JSC::SourceProviderSourceType::Module, script.copyRef());
345
346     ASSERT(m_element.document().contentSecurityPolicy());
347     const auto& contentSecurityPolicy = *m_element.document().contentSecurityPolicy();
348     bool hasKnownNonce = contentSecurityPolicy.allowScriptWithNonce(nonce, m_element.isInUserAgentShadowTree());
349     if (!contentSecurityPolicy.allowInlineScript(m_element.document().url(), m_startLineNumber, sourceCode.source().toStringWithoutCopying(), hasKnownNonce))
350         return false;
351
352     script->load(m_element.document(), sourceCode);
353     m_loadableScript = WTFMove(script);
354     return true;
355 }
356
357 void ScriptElement::executeClassicScript(const ScriptSourceCode& sourceCode)
358 {
359     ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
360     ASSERT(m_alreadyStarted);
361
362     if (sourceCode.isEmpty())
363         return;
364
365     if (!m_isExternalScript) {
366         ASSERT(m_element.document().contentSecurityPolicy());
367         const ContentSecurityPolicy& contentSecurityPolicy = *m_element.document().contentSecurityPolicy();
368         bool hasKnownNonce = contentSecurityPolicy.allowScriptWithNonce(m_element.attributeWithoutSynchronization(HTMLNames::nonceAttr), m_element.isInUserAgentShadowTree());
369         if (!contentSecurityPolicy.allowInlineScript(m_element.document().url(), m_startLineNumber, sourceCode.source().toStringWithoutCopying(), hasKnownNonce))
370             return;
371     }
372
373     auto& document = m_element.document();
374     auto* frame = document.frame();
375     if (!frame)
376         return;
377
378     IgnoreDestructiveWriteCountIncrementer ignoreDesctructiveWriteCountIncrementer(m_isExternalScript ? &document : nullptr);
379     CurrentScriptIncrementer currentScriptIncrementer(document, m_element);
380
381     frame->script().evaluate(sourceCode);
382 }
383
384 void ScriptElement::executeModuleScript(LoadableModuleScript& loadableModuleScript)
385 {
386     // https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block
387
388     ASSERT(!loadableModuleScript.error());
389
390     auto& document = m_element.document();
391     auto* frame = document.frame();
392     if (!frame)
393         return;
394
395     IgnoreDestructiveWriteCountIncrementer ignoreDesctructiveWriteCountIncrementer(&document);
396     CurrentScriptIncrementer currentScriptIncrementer(document, m_element);
397
398     frame->script().linkAndEvaluateModuleScript(loadableModuleScript);
399 }
400
401 void ScriptElement::executeScriptAndDispatchEvent(LoadableScript& loadableScript)
402 {
403     if (std::optional<LoadableScript::Error> error = loadableScript.error()) {
404         if (std::optional<LoadableScript::ConsoleMessage> message = error->consoleMessage)
405             m_element.document().addConsoleMessage(message->source, message->level, message->message);
406         dispatchErrorEvent();
407     } else if (!loadableScript.wasCanceled()) {
408         ASSERT(!loadableScript.error());
409         loadableScript.execute(*this);
410         dispatchLoadEvent();
411     }
412 }
413
414 void ScriptElement::executePendingScript(PendingScript& pendingScript)
415 {
416     if (auto* loadableScript = pendingScript.loadableScript())
417         executeScriptAndDispatchEvent(*loadableScript);
418     else {
419         ASSERT(!pendingScript.error());
420         ASSERT_WITH_MESSAGE(scriptType() == ScriptType::Classic, "Module script always have a loadableScript pointer.");
421         executeClassicScript(ScriptSourceCode(scriptContent(), m_element.document().url(), pendingScript.startingPosition(), JSC::SourceProviderSourceType::Program, InlineClassicScript::create(*this)));
422         dispatchLoadEvent();
423     }
424 }
425
426 bool ScriptElement::ignoresLoadRequest() const
427 {
428     return m_alreadyStarted || m_isExternalScript || m_parserInserted || !m_element.isConnected();
429 }
430
431 bool ScriptElement::isScriptForEventSupported() const
432 {
433     String eventAttribute = eventAttributeValue();
434     String forAttribute = forAttributeValue();
435     if (!eventAttribute.isNull() && !forAttribute.isNull()) {
436         forAttribute = stripLeadingAndTrailingHTMLSpaces(forAttribute);
437         if (!equalLettersIgnoringASCIICase(forAttribute, "window"))
438             return false;
439
440         eventAttribute = stripLeadingAndTrailingHTMLSpaces(eventAttribute);
441         if (!equalLettersIgnoringASCIICase(eventAttribute, "onload") && !equalLettersIgnoringASCIICase(eventAttribute, "onload()"))
442             return false;
443     }
444     return true;
445 }
446
447 String ScriptElement::scriptContent() const
448 {
449     StringBuilder result;
450     for (auto* text = TextNodeTraversal::firstChild(m_element); text; text = TextNodeTraversal::nextSibling(*text))
451         result.append(text->data());
452     return result.toString();
453 }
454
455 void ScriptElement::ref()
456 {
457     m_element.ref();
458 }
459
460 void ScriptElement::deref()
461 {
462     m_element.deref();
463 }
464
465 bool isScriptElement(Element& element)
466 {
467     return is<HTMLScriptElement>(element) || is<SVGScriptElement>(element);
468 }
469
470 ScriptElement& downcastScriptElement(Element& element)
471 {
472     if (is<HTMLScriptElement>(element))
473         return downcast<HTMLScriptElement>(element);
474     return downcast<SVGScriptElement>(element);
475 }
476
477 }