Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / dom / script / ScriptElement.cpp
blobe6ba15d7adec93ed8039cade73ecc66807f6749c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ScriptElement.h"
8 #include "ScriptLoader.h"
9 #include "mozilla/BasicEvents.h"
10 #include "mozilla/CycleCollectedJSContext.h"
11 #include "mozilla/EventDispatcher.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/dom/Element.h"
14 #include "mozilla/dom/MutationEventBinding.h"
15 #include "nsContentUtils.h"
16 #include "nsThreadUtils.h"
17 #include "nsPresContext.h"
18 #include "nsIParser.h"
19 #include "nsGkAtoms.h"
20 #include "nsContentSink.h"
22 using namespace mozilla;
23 using namespace mozilla::dom;
25 NS_IMETHODIMP
26 ScriptElement::ScriptAvailable(nsresult aResult, nsIScriptElement* aElement,
27 bool aIsInlineClassicScript, nsIURI* aURI,
28 uint32_t aLineNo) {
29 if (!aIsInlineClassicScript && NS_FAILED(aResult)) {
30 nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
31 if (parser) {
32 nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
33 if (sink) {
34 nsCOMPtr<Document> parserDoc = do_QueryInterface(sink->GetTarget());
35 if (GetAsContent()->OwnerDoc() != parserDoc) {
36 // Suppress errors when we've moved between docs.
37 // /html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-module.html
38 // See also https://bugzilla.mozilla.org/show_bug.cgi?id=1849107
39 return NS_OK;
44 if (parser) {
45 parser->IncrementScriptNestingLevel();
47 nsresult rv = FireErrorEvent();
48 if (parser) {
49 parser->DecrementScriptNestingLevel();
51 return rv;
53 return NS_OK;
56 /* virtual */
57 nsresult ScriptElement::FireErrorEvent() {
58 nsIContent* cont = GetAsContent();
60 return nsContentUtils::DispatchTrustedEvent(
61 cont->OwnerDoc(), cont, u"error"_ns, CanBubble::eNo, Cancelable::eNo);
64 NS_IMETHODIMP
65 ScriptElement::ScriptEvaluated(nsresult aResult, nsIScriptElement* aElement,
66 bool aIsInline) {
67 nsresult rv = NS_OK;
68 if (!aIsInline) {
69 nsCOMPtr<nsIContent> cont = GetAsContent();
71 RefPtr<nsPresContext> presContext =
72 nsContentUtils::GetContextForContent(cont);
74 nsEventStatus status = nsEventStatus_eIgnore;
75 EventMessage message = NS_SUCCEEDED(aResult) ? eLoad : eLoadError;
76 WidgetEvent event(true, message);
77 // Load event doesn't bubble.
78 event.mFlags.mBubbles = (message != eLoad);
80 EventDispatcher::Dispatch(cont, presContext, &event, nullptr, &status);
83 return rv;
86 void ScriptElement::CharacterDataChanged(nsIContent* aContent,
87 const CharacterDataChangeInfo&) {
88 MaybeProcessScript();
91 void ScriptElement::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
92 nsAtom* aAttribute, int32_t aModType,
93 const nsAttrValue* aOldValue) {
94 // https://html.spec.whatwg.org/#script-processing-model
95 // When a script element el that is not parser-inserted experiences one of the
96 // events listed in the following list, the user agent must immediately
97 // prepare the script element el:
98 // - The script element is connected and has a src attribute set where
99 // previously the element had no such attribute.
100 if (aElement->IsSVGElement() && ((aNameSpaceID != kNameSpaceID_XLink &&
101 aNameSpaceID != kNameSpaceID_None) ||
102 aAttribute != nsGkAtoms::href)) {
103 return;
105 if (aElement->IsHTMLElement() &&
106 (aNameSpaceID != kNameSpaceID_None || aAttribute != nsGkAtoms::src)) {
107 return;
109 if (mParserCreated == NOT_FROM_PARSER &&
110 aModType == MutationEvent_Binding::ADDITION) {
111 auto* cont = GetAsContent();
112 if (cont->IsInComposedDoc()) {
113 MaybeProcessScript();
118 void ScriptElement::ContentAppended(nsIContent* aFirstNewContent) {
119 MaybeProcessScript();
122 void ScriptElement::ContentInserted(nsIContent* aChild) {
123 MaybeProcessScript();
126 bool ScriptElement::MaybeProcessScript() {
127 nsIContent* cont = GetAsContent();
129 NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.contains(this),
130 "You forgot to add self as observer");
132 // https://html.spec.whatwg.org/#parsing-main-incdata
133 // An end tag whose tag name is "script"
134 // - If the active speculative HTML parser is null and the JavaScript
135 // execution context stack is empty, then perform a microtask checkpoint.
136 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
137 "ScriptElement::MaybeProcessScript", []() { nsAutoMicroTask mt; }));
139 if (mAlreadyStarted || !mDoneAddingChildren || !cont->GetComposedDoc() ||
140 mMalformed) {
141 return false;
144 if (!HasScriptContent()) {
145 // In the case of an empty, non-external classic script, there is nothing
146 // to process. However, we must perform a microtask checkpoint afterwards,
147 // as per https://html.spec.whatwg.org/#clean-up-after-running-script
148 if (mKind == JS::loader::ScriptKind::eClassic && !mExternal) {
149 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
150 "ScriptElement::MaybeProcessScript", []() { nsAutoMicroTask mt; }));
152 return false;
155 // Check the type attribute to determine language and version. If type exists,
156 // it trumps the deprecated 'language='.
157 nsAutoString type;
158 bool hasType = GetScriptType(type);
159 if (!type.IsEmpty()) {
160 NS_ENSURE_TRUE(nsContentUtils::IsJavascriptMIMEType(type) ||
161 type.LowerCaseEqualsASCII("module") ||
162 type.LowerCaseEqualsASCII("importmap"),
163 false);
164 } else if (!hasType) {
165 // "language" is a deprecated attribute of HTML, so we check it only for
166 // HTML script elements.
167 if (cont->IsHTMLElement()) {
168 nsAutoString language;
169 cont->AsElement()->GetAttr(nsGkAtoms::language, language);
170 if (!language.IsEmpty() &&
171 !nsContentUtils::IsJavaScriptLanguage(language)) {
172 return false;
177 Document* ownerDoc = cont->OwnerDoc();
178 FreezeExecutionAttrs(ownerDoc);
180 mAlreadyStarted = true;
182 nsCOMPtr<nsIParser> parser = ((nsIScriptElement*)this)->GetCreatorParser();
183 if (parser) {
184 nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
185 if (sink) {
186 nsCOMPtr<Document> parserDoc = do_QueryInterface(sink->GetTarget());
187 if (ownerDoc != parserDoc) {
188 // Refactor this: https://bugzilla.mozilla.org/show_bug.cgi?id=1849107
189 return false;
194 RefPtr<ScriptLoader> loader = ownerDoc->ScriptLoader();
195 return loader->ProcessScriptElement(this, type);
198 bool ScriptElement::GetScriptType(nsAString& aType) {
199 Element* element = GetAsContent()->AsElement();
201 nsAutoString type;
202 if (!element->GetAttr(nsGkAtoms::type, type)) {
203 return false;
206 // ASCII whitespace https://infra.spec.whatwg.org/#ascii-whitespace:
207 // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE.
208 static const char kASCIIWhitespace[] = "\t\n\f\r ";
210 const bool wasEmptyBeforeTrim = type.IsEmpty();
211 type.Trim(kASCIIWhitespace);
213 // If the value before trim was not empty and the value is now empty, do not
214 // trim as we want to retain pure whitespace (by restoring original value)
215 // because we need to treat "" and " " (etc) differently.
216 if (!wasEmptyBeforeTrim && type.IsEmpty()) {
217 return element->GetAttr(nsGkAtoms::type, aType);
220 aType.Assign(type);
221 return true;