Bug 1361369 - Allow async attribute on inline module scripts r=smaug
authorJon Coppeard <jcoppeard@mozilla.com>
Mon, 08 Jan 2018 15:17:34 +0000
changeset 450005 d4a7a8196fc3db520b7c0828b793d21bc9d7a2ef
parent 450004 697b4f431bf032e784f1813eb7743eb8f2dc6328
child 450006 db30c73b1542d381d4240f43d76225478105f894
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1361369
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1361369 - Allow async attribute on inline module scripts r=smaug
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/html/HTMLScriptElement.cpp
dom/html/HTMLScriptElement.h
dom/script/ScriptElement.cpp
dom/script/ScriptLoader.cpp
dom/script/ScriptLoader.h
dom/script/nsIScriptElement.h
dom/svg/SVGScriptElement.cpp
dom/svg/SVGScriptElement.h
parser/html/nsHtml5TreeOperation.cpp
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -14003,8 +14003,21 @@ nsDocument::RecordNavigationTiming(Ready
         mDOMCompleteSet = true;
       }
       break;
     default:
       NS_WARNING("Unexpected ReadyState value");
       break;
   }
 }
+
+bool
+nsIDocument::ModuleScriptsEnabled()
+{
+  static bool sEnabledForContent = false;
+  static bool sCachedPref = false;
+  if (!sCachedPref) {
+    sCachedPref = true;
+    Preferences::AddBoolVarCache(&sEnabledForContent, "dom.moduleScripts.enabled", false);
+  }
+
+  return nsContentUtils::IsChromeDoc(this) || sEnabledForContent;
+}
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -3217,16 +3217,18 @@ public:
   virtual bool AllowPaymentRequest() const = 0;
   virtual void SetAllowPaymentRequest(bool aAllowPaymentRequest) = 0;
 
   bool IsWebComponentsEnabled() const
   {
     return mIsWebComponentsEnabled;
   }
 
+  bool ModuleScriptsEnabled();
+
 protected:
   bool GetUseCounter(mozilla::UseCounter aUseCounter)
   {
     return mUseCounters[aUseCounter];
   }
 
   void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
   {
--- a/dom/html/HTMLScriptElement.cpp
+++ b/dom/html/HTMLScriptElement.cpp
@@ -185,22 +185,30 @@ HTMLScriptElement::GetScriptText(nsAStri
 
 void
 HTMLScriptElement::GetScriptCharset(nsAString& charset)
 {
   GetCharset(charset);
 }
 
 void
-HTMLScriptElement::FreezeUriAsyncDefer()
+HTMLScriptElement::FreezeExecutionAttrs(nsIDocument* aOwnerDoc)
 {
   if (mFrozen) {
     return;
   }
 
+  MOZ_ASSERT(!mIsModule && !mAsync && !mDefer && !mExternal);
+
+  // Determine whether this is a classic script or a module script.
+  nsAutoString type;
+  GetScriptType(type);
+  mIsModule = aOwnerDoc->ModuleScriptsEnabled() &&
+              !type.IsEmpty() && type.LowerCaseEqualsASCII("module");
+
   // variation of this code in nsSVGScriptElement - check if changes
   // need to be transfered when modifying.  Note that we don't use GetSrc here
   // because it will return the base URL when the attr value is "".
   nsAutoString src;
   if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
     // Empty src should be treated as invalid URL.
     if (!src.IsEmpty()) {
       nsCOMPtr<nsIURI> baseURI = GetBaseURI();
@@ -223,23 +231,23 @@ HTMLScriptElement::FreezeUriAsyncDefer()
         NS_LITERAL_CSTRING("HTML"), OwnerDoc(),
         nsContentUtils::eDOM_PROPERTIES, "ScriptSourceEmpty",
         params, ArrayLength(params), nullptr,
         EmptyString(), GetScriptLineNumber());
     }
 
     // At this point mUri will be null for invalid URLs.
     mExternal = true;
-
-    bool async = Async();
-    bool defer = Defer();
+  }
 
-    mDefer = !async && defer;
-    mAsync = async;
-  }
+  bool async = (mExternal || mIsModule) && Async();
+  bool defer = mExternal && Defer();
+
+  mDefer = !async && defer;
+  mAsync = async;
 
   mFrozen = true;
 }
 
 CORSMode
 HTMLScriptElement::GetCORSMode() const
 {
   return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
--- a/dom/html/HTMLScriptElement.h
+++ b/dom/html/HTMLScriptElement.h
@@ -31,17 +31,17 @@ public:
   virtual void SetInnerHTML(const nsAString& aInnerHTML,
                             nsIPrincipal* aSubjectPrincipal,
                             mozilla::ErrorResult& aError) override;
 
   // nsIScriptElement
   virtual bool GetScriptType(nsAString& type) override;
   virtual void GetScriptText(nsAString& text) override;
   virtual void GetScriptCharset(nsAString& charset) override;
-  virtual void FreezeUriAsyncDefer() override;
+  virtual void FreezeExecutionAttrs(nsIDocument* aOwnerDoc) override;
   virtual CORSMode GetCORSMode() const override;
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual bool ParseAttribute(int32_t aNamespaceID,
                               nsAtom* aAttribute,
--- a/dom/script/ScriptElement.cpp
+++ b/dom/script/ScriptElement.cpp
@@ -121,21 +121,21 @@ ScriptElement::MaybeProcessScript()
   NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.Contains(this),
                "You forgot to add self as observer");
 
   if (mAlreadyStarted || !mDoneAddingChildren ||
       !cont->GetComposedDoc() || mMalformed || !HasScriptContent()) {
     return false;
   }
 
-  FreezeUriAsyncDefer();
+  nsIDocument* ownerDoc = cont->OwnerDoc();
+  FreezeExecutionAttrs(ownerDoc);
 
   mAlreadyStarted = true;
 
-  nsIDocument* ownerDoc = cont->OwnerDoc();
   nsCOMPtr<nsIParser> parser = ((nsIScriptElement*) this)->GetCreatorParser();
   if (parser) {
     nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
     if (sink) {
       nsCOMPtr<nsIDocument> parserDoc = do_QueryInterface(sink->GetTarget());
       if (ownerDoc != parserDoc) {
         // Willful violation of HTML5 as of 2010-12-01
         return false;
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -323,29 +323,16 @@ ScriptLoader::CheckContentPolicy(nsIDocu
     }
     return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
   }
 
   return NS_OK;
 }
 
 bool
-ScriptLoader::ModuleScriptsEnabled()
-{
-  static bool sEnabledForContent = false;
-  static bool sCachedPref = false;
-  if (!sCachedPref) {
-    sCachedPref = true;
-    Preferences::AddBoolVarCache(&sEnabledForContent, "dom.moduleScripts.enabled", false);
-  }
-
-  return nsContentUtils::IsChromeDoc(mDocument) || sEnabledForContent;
-}
-
-bool
 ScriptLoader::ModuleMapContainsURL(nsIURI* aURL) const
 {
   // Returns whether we have fetched, or are currently fetching, a module script
   // for a URL.
   return mFetchingModules.Contains(aURL) ||
          mFetchedModules.Contains(aURL);
 }
 
@@ -1310,24 +1297,21 @@ ScriptLoader::ProcessScriptElement(nsISc
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
     return false;
   }
 
   NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
 
   nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
 
-  // Determine whether this is a classic script or a module script.
   nsAutoString type;
   bool hasType = aElement->GetScriptType(type);
-  ScriptKind scriptKind = ScriptKind::eClassic;
-  if (ModuleScriptsEnabled() &&
-      !type.IsEmpty() && type.LowerCaseEqualsASCII("module")) {
-    scriptKind = ScriptKind::eModule;
-  }
+
+  ScriptKind scriptKind =
+    aElement->GetScriptIsModule() ? ScriptKind::eModule : ScriptKind::eClassic;
 
   // Step 13. Check that the script is not an eventhandler
   if (IsScriptEventHandler(scriptKind, scriptContent)) {
     return false;
   }
 
   ValidJSVersion validJSVersion = ValidJSVersion::eValid;
 
@@ -1353,17 +1337,17 @@ ScriptLoader::ProcessScriptElement(nsISc
       }
     }
   }
 
   // "In modern user agents that support module scripts, the script element with
   // the nomodule attribute will be ignored".
   // "The nomodule attribute must not be specified on module scripts (and will
   // be ignored if it is)."
-  if (ModuleScriptsEnabled() &&
+  if (mDocument->ModuleScriptsEnabled() &&
       scriptKind == ScriptKind::eClassic &&
       scriptContent->IsHTMLElement() &&
       scriptContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::nomodule)) {
     return false;
   }
 
   // Step 15. and later in the HTML5 spec
   nsresult rv = NS_OK;
@@ -1586,16 +1570,22 @@ ScriptLoader::ProcessScriptElement(nsISc
   request->mIsInline = true;
   request->mTriggeringPrincipal = mDocument->NodePrincipal();
   request->mLineNo = aElement->GetScriptLineNumber();
   request->mProgress = ScriptLoadRequest::Progress::eLoading_Source;
   request->mDataType = ScriptLoadRequest::DataType::eSource;
   TRACE_FOR_TEST_BOOL(request->mElement, "scriptloader_load_source");
   CollectScriptTelemetry(nullptr, request);
 
+  // Only the 'async' attribute is heeded on an inline module script and
+  // inline classic scripts ignore both these attributes.
+  MOZ_ASSERT(!aElement->GetScriptDeferred());
+  MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync());
+  request->SetScriptMode(false, aElement->GetScriptAsync());
+
   LOG(("ScriptLoadRequest (%p): Created request for inline script",
        request.get()));
 
   if (request->IsModuleRequest()) {
     ModuleLoadRequest* modReq = request->AsModuleRequest();
     modReq->mBaseURL = mDocument->GetDocBaseURI();
 
     if (aElement->GetParserCreated() != NOT_FROM_PARSER) {
@@ -3198,17 +3188,17 @@ ScriptLoader::PreloadURI(nsIURI* aURI, c
 {
   NS_ENSURE_TRUE_VOID(mDocument);
   // Check to see if scripts has been turned off.
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
     return;
   }
 
   // TODO: Preload module scripts.
-  if (ModuleScriptsEnabled() && aType.LowerCaseEqualsASCII("module")) {
+  if (mDocument->ModuleScriptsEnabled() && aType.LowerCaseEqualsASCII("module")) {
     return;
   }
 
   SRIMetadata sriMetadata;
   if (!aIntegrity.IsEmpty()) {
     MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
             ("ScriptLoader::PreloadURI, integrity=%s",
              NS_ConvertUTF16toUTF8(aIntegrity).get()));
--- a/dom/script/ScriptLoader.h
+++ b/dom/script/ScriptLoader.h
@@ -473,18 +473,16 @@ private:
   void AddAsyncRequest(ScriptLoadRequest* aRequest);
   bool MaybeRemovedDeferRequests();
 
   void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
 
   JS::SourceBufferHolder GetScriptSource(ScriptLoadRequest* aRequest,
                                          nsAutoString& inlineData);
 
-  bool ModuleScriptsEnabled();
-
   void SetModuleFetchStarted(ModuleLoadRequest *aRequest);
   void SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest* aRequest,
                                                       nsresult aResult);
 
   bool IsFetchingModule(ModuleLoadRequest* aRequest) const;
 
   bool ModuleMapContainsURL(nsIURI* aURL) const;
   RefPtr<mozilla::GenericPromise> WaitForModuleFetch(nsIURI* aURL);
--- a/dom/script/nsIScriptElement.h
+++ b/dom/script/nsIScriptElement.h
@@ -33,16 +33,17 @@ public:
     : mLineNumber(1),
       mAlreadyStarted(false),
       mMalformed(false),
       mDoneAddingChildren(aFromParser == mozilla::dom::NOT_FROM_PARSER ||
                           aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT),
       mForceAsync(aFromParser == mozilla::dom::NOT_FROM_PARSER ||
                   aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT),
       mFrozen(false),
+      mIsModule(false),
       mDefer(false),
       mAsync(false),
       mExternal(false),
       mParserCreated(aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT ?
                      mozilla::dom::NOT_FROM_PARSER : aFromParser),
                      // Fragment parser-created scripts (if executable)
                      // behave like script-created scripts.
       mCreatorParser(nullptr)
@@ -75,21 +76,34 @@ public:
   /**
    * Script source text for inline script elements.
    */
   virtual void GetScriptText(nsAString& text) = 0;
 
   virtual void GetScriptCharset(nsAString& charset) = 0;
 
   /**
-   * Freezes the return values of GetScriptDeferred(), GetScriptAsync() and
-   * GetScriptURI() so that subsequent modifications to the attributes don't
-   * change execution behavior.
+   * Freezes the return values of the following methods so that subsequent
+   * modifications to the attributes don't change execution behavior:
+   *  - GetScriptIsModule()
+   *  - GetScriptDeferred()
+   *  - GetScriptAsync()
+   *  - GetScriptURI()
+   *  - GetScriptExternal()
    */
-  virtual void FreezeUriAsyncDefer() = 0;
+  virtual void FreezeExecutionAttrs(nsIDocument* aOwnerDoc) = 0;
+
+  /**
+   * Is the script a module script. Currently only supported by HTML scripts.
+   */
+  bool GetScriptIsModule()
+  {
+    NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
+    return mIsModule;
+  }
 
   /**
    * Is the script deferred. Currently only supported by HTML scripts.
    */
   bool GetScriptDeferred()
   {
     NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
     return mDefer;
@@ -298,16 +312,21 @@ protected:
   bool mForceAsync;
 
   /**
    * Whether src, defer and async are frozen.
    */
   bool mFrozen;
 
   /**
+   * The effective moduleness.
+   */
+  bool mIsModule;
+
+  /**
    * The effective deferredness.
    */
   bool mDefer;
 
   /**
    * The effective asyncness.
    */
   bool mAsync;
--- a/dom/svg/SVGScriptElement.cpp
+++ b/dom/svg/SVGScriptElement.cpp
@@ -134,17 +134,17 @@ SVGScriptElement::GetScriptText(nsAStrin
 
 void
 SVGScriptElement::GetScriptCharset(nsAString& charset)
 {
   charset.Truncate();
 }
 
 void
-SVGScriptElement::FreezeUriAsyncDefer()
+SVGScriptElement::FreezeExecutionAttrs(nsIDocument* aOwnerDoc)
 {
   if (mFrozen) {
     return;
   }
 
   if (mStringAttributes[HREF].IsExplicitlySet() ||
       mStringAttributes[XLINK_HREF].IsExplicitlySet()) {
     // variation of this code in nsHTMLScriptElement - check if changes
--- a/dom/svg/SVGScriptElement.h
+++ b/dom/svg/SVGScriptElement.h
@@ -39,17 +39,17 @@ public:
   // interfaces:
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIScriptElement
   virtual bool GetScriptType(nsAString& type) override;
   virtual void GetScriptText(nsAString& text) override;
   virtual void GetScriptCharset(nsAString& charset) override;
-  virtual void FreezeUriAsyncDefer() override;
+  virtual void FreezeExecutionAttrs(nsIDocument* aOwnerDoc) override;
   virtual CORSMode GetCORSMode() const override;
 
   // ScriptElement
   virtual bool HasScriptContent() override;
 
   // nsIContent specializations:
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -1052,17 +1052,17 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
       }
       return NS_OK;
     }
     case eTreeOpSetScriptLineNumberAndFreeze: {
       nsIContent* node = *(mOne.node);
       nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(node);
       if (sele) {
         sele->SetScriptLineNumber(mFour.integer);
-        sele->FreezeUriAsyncDefer();
+        sele->FreezeExecutionAttrs(node->OwnerDoc());
       } else {
         MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, "Node didn't QI to script, but SVG wasn't disabled.");
       }
       return NS_OK;
     }
     case eTreeOpSvgLoad: {
       nsIContent* node = *(mOne.node);
       SvgLoad(node);