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
;
26 ScriptElement::ScriptAvailable(nsresult aResult
, nsIScriptElement
* aElement
,
27 bool aIsInlineClassicScript
, nsIURI
* aURI
,
29 if (!aIsInlineClassicScript
&& NS_FAILED(aResult
)) {
30 nsCOMPtr
<nsIParser
> parser
= do_QueryReferent(mCreatorParser
);
32 nsCOMPtr
<nsIContentSink
> sink
= parser
->GetContentSink();
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
45 parser
->IncrementScriptNestingLevel();
47 nsresult rv
= FireErrorEvent();
49 parser
->DecrementScriptNestingLevel();
57 nsresult
ScriptElement::FireErrorEvent() {
58 nsIContent
* cont
= GetAsContent();
60 return nsContentUtils::DispatchTrustedEvent(
61 cont
->OwnerDoc(), cont
, u
"error"_ns
, CanBubble::eNo
, Cancelable::eNo
);
65 ScriptElement::ScriptEvaluated(nsresult aResult
, nsIScriptElement
* aElement
,
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
);
86 void ScriptElement::CharacterDataChanged(nsIContent
* aContent
,
87 const CharacterDataChangeInfo
&) {
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
)) {
105 if (aElement
->IsHTMLElement() &&
106 (aNameSpaceID
!= kNameSpaceID_None
|| aAttribute
!= nsGkAtoms::src
)) {
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() ||
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
; }));
155 // Check the type attribute to determine language and version. If type exists,
156 // it trumps the deprecated 'language='.
158 bool hasType
= GetScriptType(type
);
159 if (!type
.IsEmpty()) {
160 NS_ENSURE_TRUE(nsContentUtils::IsJavascriptMIMEType(type
) ||
161 type
.LowerCaseEqualsASCII("module") ||
162 type
.LowerCaseEqualsASCII("importmap"),
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
)) {
177 Document
* ownerDoc
= cont
->OwnerDoc();
178 FreezeExecutionAttrs(ownerDoc
);
180 mAlreadyStarted
= true;
182 nsCOMPtr
<nsIParser
> parser
= ((nsIScriptElement
*)this)->GetCreatorParser();
184 nsCOMPtr
<nsIContentSink
> sink
= parser
->GetContentSink();
186 nsCOMPtr
<Document
> parserDoc
= do_QueryInterface(sink
->GetTarget());
187 if (ownerDoc
!= parserDoc
) {
188 // Refactor this: https://bugzilla.mozilla.org/show_bug.cgi?id=1849107
194 RefPtr
<ScriptLoader
> loader
= ownerDoc
->ScriptLoader();
195 return loader
->ProcessScriptElement(this, type
);
198 bool ScriptElement::GetScriptType(nsAString
& aType
) {
199 Element
* element
= GetAsContent()->AsElement();
202 if (!element
->GetAttr(nsGkAtoms::type
, type
)) {
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
);