ScriptElement.cpp (13698B)
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/. */ 6 7 #include "ScriptElement.h" 8 9 #include "ScriptLoader.h" 10 #include "mozilla/BasicEvents.h" 11 #include "mozilla/CycleCollectedJSContext.h" 12 #include "mozilla/EventDispatcher.h" 13 #include "mozilla/StaticPrefs_dom.h" 14 #include "mozilla/dom/Document.h" 15 #include "mozilla/dom/Element.h" 16 #include "mozilla/dom/TrustedTypeUtils.h" 17 #include "mozilla/dom/TrustedTypesConstants.h" 18 #include "mozilla/extensions/WebExtensionPolicy.h" 19 #include "nsContentSink.h" 20 #include "nsContentUtils.h" 21 #include "nsGkAtoms.h" 22 #include "nsIMutationObserver.h" 23 #include "nsIParser.h" 24 #include "nsPresContext.h" 25 #include "nsThreadUtils.h" 26 27 using namespace mozilla; 28 using namespace mozilla::dom; 29 30 NS_IMETHODIMP 31 ScriptElement::ScriptAvailable(nsresult aResult, nsIScriptElement* aElement, 32 bool aIsInlineClassicScript, nsIURI* aURI, 33 uint32_t aLineNo) { 34 if (!aIsInlineClassicScript && NS_FAILED(aResult)) { 35 nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser); 36 if (parser) { 37 nsCOMPtr<nsIContentSink> sink = parser->GetContentSink(); 38 if (sink) { 39 nsCOMPtr<Document> parserDoc = do_QueryInterface(sink->GetTarget()); 40 if (GetAsContent()->OwnerDoc() != parserDoc) { 41 // Suppress errors when we've moved between docs. 42 // /html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-fetch-error-external-module.html 43 // See also https://bugzilla.mozilla.org/show_bug.cgi?id=1849107 44 return NS_OK; 45 } 46 } 47 } 48 49 if (parser) { 50 parser->IncrementScriptNestingLevel(); 51 } 52 nsresult rv = FireErrorEvent(); 53 if (parser) { 54 parser->DecrementScriptNestingLevel(); 55 } 56 return rv; 57 } 58 return NS_OK; 59 } 60 61 /* virtual */ 62 nsresult ScriptElement::FireErrorEvent() { 63 nsIContent* cont = GetAsContent(); 64 65 return nsContentUtils::DispatchTrustedEvent( 66 cont->OwnerDoc(), cont, u"error"_ns, CanBubble::eNo, Cancelable::eNo); 67 } 68 69 NS_IMETHODIMP 70 ScriptElement::ScriptEvaluated(nsresult aResult, nsIScriptElement* aElement, 71 bool aIsInline) { 72 nsresult rv = NS_OK; 73 if (!aIsInline) { 74 nsCOMPtr<nsIContent> cont = GetAsContent(); 75 76 RefPtr<nsPresContext> presContext = 77 nsContentUtils::GetContextForContent(cont); 78 79 nsEventStatus status = nsEventStatus_eIgnore; 80 EventMessage message = NS_SUCCEEDED(aResult) ? eLoad : eLoadError; 81 WidgetEvent event(true, message); 82 // Load event doesn't bubble. 83 event.mFlags.mBubbles = (message != eLoad); 84 85 EventDispatcher::Dispatch(cont, presContext, &event, nullptr, &status); 86 } 87 88 return rv; 89 } 90 91 void ScriptElement::CharacterDataChanged(nsIContent* aContent, 92 const CharacterDataChangeInfo& aInfo) { 93 UpdateTrustWorthiness(aInfo.mMutationEffectOnScript); 94 MaybeProcessScript(nullptr /* aParser */); 95 } 96 97 void ScriptElement::AttributeChanged(Element* aElement, int32_t aNameSpaceID, 98 nsAtom* aAttribute, AttrModType aModType, 99 const nsAttrValue* aOldValue) { 100 // https://html.spec.whatwg.org/#script-processing-model 101 // When a script element el that is not parser-inserted experiences one of the 102 // events listed in the following list, the user agent must immediately 103 // prepare the script element el: 104 // - The script element is connected and has a src attribute set where 105 // previously the element had no such attribute. 106 if (aElement->IsSVGElement() && ((aNameSpaceID != kNameSpaceID_XLink && 107 aNameSpaceID != kNameSpaceID_None) || 108 aAttribute != nsGkAtoms::href)) { 109 return; 110 } 111 if (aElement->IsHTMLElement() && 112 (aNameSpaceID != kNameSpaceID_None || aAttribute != nsGkAtoms::src)) { 113 return; 114 } 115 if (mParserCreated == NOT_FROM_PARSER && aModType == AttrModType::Addition) { 116 auto* cont = GetAsContent(); 117 if (cont->IsInComposedDoc()) { 118 MaybeProcessScript(nullptr /* aParser */); 119 } 120 } 121 } 122 123 void ScriptElement::ContentAppended(nsIContent* aFirstNewContent, 124 const ContentAppendInfo& aInfo) { 125 UpdateTrustWorthiness(aInfo.mMutationEffectOnScript); 126 MaybeProcessScript(nullptr /* aParser */); 127 } 128 129 void ScriptElement::ContentInserted(nsIContent* aChild, 130 const ContentInsertInfo& aInfo) { 131 UpdateTrustWorthiness(aInfo.mMutationEffectOnScript); 132 MaybeProcessScript(nullptr /* aParser */); 133 } 134 135 void ScriptElement::ContentWillBeRemoved(nsIContent* aChild, 136 const ContentRemoveInfo& aInfo) { 137 UpdateTrustWorthiness(aInfo.mMutationEffectOnScript); 138 } 139 140 bool ScriptElement::MaybeProcessScript(nsCOMPtr<nsIParser> aParser) { 141 nsIContent* cont = GetAsContent(); 142 143 NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.contains(this), 144 "You forgot to add self as observer"); 145 146 if (mAlreadyStarted || !mDoneAddingChildren || !cont->GetComposedDoc() || 147 mMalformed) { 148 return false; 149 } 150 151 // https://html.spec.whatwg.org/#prepare-the-script-element 152 // The spec says we should calculate "source text" of inline scripts at the 153 // beginning of the "Prepare the script element" algorithm. 154 if (HasExternalScriptContent() || mIsTrusted || 155 TrustedTypeUtils::CanSkipTrustedTypesEnforcement( 156 *GetAsContent()->AsElement())) { 157 // - If it is an inline script that is trusted, we will actually retrieve 158 // the "source text" lazily for performance reasons (see bug 1376651) so we 159 // just pass a void string to MaybeProcessScript(). 160 // - If it is an external script, we actually don't need the "source text" 161 // and can similarly pass a void string to MaybeProcessScript(). 162 bool block = MaybeProcessScript(VoidString()); 163 if (block && aParser) { 164 aParser->BlockParser(); 165 } 166 return block; 167 } 168 169 // This is an inline script that is not trusted (i.e. we must execute the 170 // Trusted Type default policy callback to obtain a trusted "source text"). 171 if (nsContentUtils::IsSafeToRunScript()) { 172 // - If it is safe to run script in this context, we run the default policy 173 // callback and pass the returned "source text" to MaybeProcessScript(). 174 bool block = 175 ([self = RefPtr<nsIScriptElement>(this)]() 176 MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { 177 nsString sourceText; 178 self->GetTrustedTypesCompliantInlineScriptText(sourceText); 179 return static_cast<ScriptElement*>(self.get()) 180 ->MaybeProcessScript(sourceText); 181 })(); 182 if (block && aParser) { 183 aParser->BlockParser(); 184 } 185 return block; 186 } 187 188 // - The default policy callback must be wrapped in a script runner. So we 189 // need to block the parser at least until we can get the "source text". 190 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( 191 "ScriptElement::MaybeProcessScript", 192 [self = RefPtr<nsIScriptElement>(this), aParser]() 193 MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { 194 nsString sourceText; 195 self->GetTrustedTypesCompliantInlineScriptText(sourceText); 196 bool block = static_cast<ScriptElement*>(self.get()) 197 ->MaybeProcessScript(sourceText); 198 if (!block && aParser) { 199 aParser->UnblockParser(); 200 } 201 })); 202 if (aParser) { 203 aParser->BlockParser(); 204 } 205 return true; 206 } 207 208 bool ScriptElement::MaybeProcessScript(const nsAString& aSourceText) { 209 nsIContent* cont = GetAsContent(); 210 if (!HasExternalScriptContent()) { 211 // If el has no src attribute, and source text is the empty string, then 212 // return (https://html.spec.whatwg.org/#prepare-the-script-element). 213 // 214 // A void aSourceText means we want to retrieve it lazily (bug 1376651), in 215 // that case we browse the subtree to try and find a non-empty text node. 216 bool hasInlineScriptContent = 217 aSourceText.IsVoid() ? nsContentUtils::HasNonEmptyTextContent(cont) 218 : !aSourceText.IsEmpty(); 219 if (!hasInlineScriptContent) { 220 // In the case of an empty, non-external classic script, there is nothing 221 // to process. However, we must perform a microtask checkpoint afterwards, 222 // as per https://html.spec.whatwg.org/#clean-up-after-running-script 223 if (mKind == JS::loader::ScriptKind::eClassic && !mExternal) { 224 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( 225 "ScriptElement::MaybeProcessScript", []() { nsAutoMicroTask mt; })); 226 } 227 return false; 228 } 229 } 230 231 // Check the type attribute to determine language and version. If type exists, 232 // it trumps the deprecated 'language='. 233 nsAutoString type; 234 bool hasType = GetScriptType(type); 235 if (!type.IsEmpty()) { 236 if (!nsContentUtils::IsJavascriptMIMEType(type) && 237 !type.LowerCaseEqualsASCII("module") && 238 !type.LowerCaseEqualsASCII("importmap")) { 239 #ifdef DEBUG 240 // There is a WebGL convention to store strings they need inside script 241 // tags with these specific unknown script types, so don't warn for them. 242 // "text/something-not-javascript" only seems to be used in the WebGL 243 // conformance tests, but it is also clearly deliberately invalid, so 244 // skip warning for it, too, to reduce warning spam. 245 if (!type.LowerCaseEqualsASCII("x-shader/x-vertex") && 246 !type.LowerCaseEqualsASCII("x-shader/x-fragment") && 247 !type.LowerCaseEqualsASCII("text/something-not-javascript")) { 248 NS_WARNING(nsPrintfCString("Unknown script type '%s'", 249 NS_ConvertUTF16toUTF8(type).get()) 250 .get()); 251 } 252 #endif // #ifdef DEBUG 253 return false; 254 } 255 } else if (!hasType) { 256 // "language" is a deprecated attribute of HTML, so we check it only for 257 // HTML script elements. 258 if (cont->IsHTMLElement()) { 259 nsAutoString language; 260 cont->AsElement()->GetAttr(nsGkAtoms::language, language); 261 if (!language.IsEmpty() && 262 !nsContentUtils::IsJavaScriptLanguage(language)) { 263 return false; 264 } 265 } 266 } 267 268 Document* ownerDoc = cont->OwnerDoc(); 269 FreezeExecutionAttrs(ownerDoc); 270 271 mAlreadyStarted = true; 272 273 nsCOMPtr<nsIParser> parser = ((nsIScriptElement*)this)->GetCreatorParser(); 274 if (parser) { 275 nsCOMPtr<nsIContentSink> sink = parser->GetContentSink(); 276 if (sink) { 277 nsCOMPtr<Document> parserDoc = do_QueryInterface(sink->GetTarget()); 278 if (ownerDoc != parserDoc) { 279 // Refactor this: https://bugzilla.mozilla.org/show_bug.cgi?id=1849107 280 return false; 281 } 282 } 283 } 284 285 RefPtr<ScriptLoader> loader = ownerDoc->GetScriptLoader(); 286 if (!loader) { 287 return false; 288 } 289 return loader->ProcessScriptElement(this, aSourceText); 290 } 291 292 bool ScriptElement::GetScriptType(nsAString& aType) { 293 Element* element = GetAsContent()->AsElement(); 294 295 nsAutoString type; 296 if (!element->GetAttr(nsGkAtoms::type, type)) { 297 return false; 298 } 299 300 // ASCII whitespace https://infra.spec.whatwg.org/#ascii-whitespace: 301 // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE. 302 static const char kASCIIWhitespace[] = "\t\n\f\r "; 303 304 const bool wasEmptyBeforeTrim = type.IsEmpty(); 305 type.Trim(kASCIIWhitespace); 306 307 // If the value before trim was not empty and the value is now empty, do not 308 // trim as we want to retain pure whitespace (by restoring original value) 309 // because we need to treat "" and " " (etc) differently. 310 if (!wasEmptyBeforeTrim && type.IsEmpty()) { 311 return element->GetAttr(nsGkAtoms::type, aType); 312 } 313 314 aType.Assign(type); 315 return true; 316 } 317 318 void ScriptElement::UpdateTrustWorthiness( 319 MutationEffectOnScript aMutationEffectOnScript) { 320 if (aMutationEffectOnScript == MutationEffectOnScript::DropTrustWorthiness && 321 StaticPrefs::dom_security_trusted_types_enabled()) { 322 nsCOMPtr<nsIPrincipal> subjectPrincipal; 323 if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) { 324 subjectPrincipal = nsContentUtils::SubjectPrincipal(cx); 325 if (auto* principal = BasePrincipal::Cast(subjectPrincipal)) { 326 if (principal->IsSystemPrincipal() || 327 principal->ContentScriptAddonPolicyCore()) { 328 // This script was modified by a priviledged scripts, so continue to 329 // consider it as trusted. 330 return; 331 } 332 } 333 } 334 335 mIsTrusted = false; 336 } 337 } 338 339 nsresult ScriptElement::GetTrustedTypesCompliantInlineScriptText( 340 nsString& aSourceText) { 341 MOZ_ASSERT(!mIsTrusted); 342 343 RefPtr<Element> element = GetAsContent()->AsElement(); 344 nsAutoString sourceText; 345 GetScriptText(sourceText); 346 347 MOZ_ASSERT(element->IsHTMLElement() || element->IsSVGElement()); 348 Maybe<nsAutoString> compliantStringHolder; 349 constexpr nsLiteralString htmlSinkName = u"HTMLScriptElement text"_ns; 350 constexpr nsLiteralString svgSinkName = u"SVGScriptElement text"_ns; 351 ErrorResult error; 352 353 const nsAString* compliantString = 354 TrustedTypeUtils::GetTrustedTypesCompliantStringForTrustedScript( 355 sourceText, element->IsHTMLElement() ? htmlSinkName : svgSinkName, 356 kTrustedTypesOnlySinkGroup, *element, compliantStringHolder, error); 357 if (!error.Failed()) { 358 aSourceText.Assign(*compliantString); 359 } 360 return error.StealNSResult(); 361 }