Bug 503481: Implement async attribute
authorJonas Sicking <jonas@sicking.cc>
Mon, 09 Nov 2009 17:04:24 -0800
changeset 34712 53689357009fc9744a3db5b4e27c336c522b89f3
parent 34711 ce753b634dd1c6b23aca035e3423c325c1aaee9b
child 34714 e74612b85463b2a8ced3acebda5e5ca3aa4af0b8
push id10198
push usersicking@mozilla.com
push dateTue, 10 Nov 2009 01:04:43 +0000
treeherdermozilla-central@53689357009f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs503481
milestone1.9.3a1pre
Bug 503481: Implement async attribute
content/base/public/nsIScriptElement.h
content/base/src/nsGkAtomList.h
content/base/src/nsScriptLoader.cpp
content/base/src/nsScriptLoader.h
content/base/test/Makefile.in
content/base/test/file_bug503481.sjs
content/base/test/file_bug503481b_inner.html
content/base/test/test_bug503481.html
content/base/test/test_bug503481b.html
content/html/content/src/nsHTMLScriptElement.cpp
content/svg/content/src/nsSVGScriptElement.cpp
dom/base/nsDOMClassInfo.cpp
dom/interfaces/html/Makefile.in
dom/interfaces/html/nsIDOMNSHTMLScriptElement.idl
--- a/content/base/public/nsIScriptElement.h
+++ b/content/base/public/nsIScriptElement.h
@@ -39,19 +39,20 @@
 #ifndef nsIScriptElement_h___
 #define nsIScriptElement_h___
 
 #include "nsISupports.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsIScriptLoaderObserver.h"
 
+// e68ddc48-4055-4ba9-978d-c49d9cf3189a
 #define NS_ISCRIPTELEMENT_IID \
-{ 0x4b916da5, 0x82c4, 0x45ab, \
-  { 0x99, 0x15, 0xcc, 0xcd, 0x9e, 0x2c, 0xb1, 0xe6 } }
+{ 0xe68ddc48, 0x4055, 0x4ba9, \
+  { 0x97, 0x8d, 0xc4, 0x9d, 0x9c, 0xf3, 0x18, 0x9a } }
 
 /**
  * Internal interface implemented by script elements
  */
 class nsIScriptElement : public nsIScriptLoaderObserver {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTELEMENT_IID)
 
@@ -82,16 +83,21 @@ public:
 
   virtual void GetScriptCharset(nsAString& charset) = 0;
 
   /**
    * Is the script deferred. Currently only supported by HTML scripts.
    */
   virtual PRBool GetScriptDeferred() = 0;
 
+  /**
+   * Is the script async. Currently only supported by HTML scripts.
+   */
+  virtual PRBool GetScriptAsync() = 0;
+
   void SetScriptLineNumber(PRUint32 aLineNumber)
   {
     mLineNumber = aLineNumber;
   }
   PRUint32 GetScriptLineNumber()
   {
     return mLineNumber;
   }
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -106,16 +106,17 @@ GK_ATOM(any, "any")
 GK_ATOM(applet, "applet")
 GK_ATOM(applyImports, "apply-imports")
 GK_ATOM(applyTemplates, "apply-templates")
 GK_ATOM(archive, "archive")
 GK_ATOM(area, "area")
 GK_ATOM(ascending, "ascending")
 GK_ATOM(aspectRatio, "aspect-ratio")
 GK_ATOM(assign, "assign")
+GK_ATOM(async, "async")
 GK_ATOM(attribute, "attribute")
 GK_ATOM(attributeSet, "attribute-set")
 GK_ATOM(aural, "aural")
 GK_ATOM(_auto, "auto")
 #ifdef MOZ_MEDIA
 GK_ATOM(autobuffer, "autobuffer")
 #endif
 GK_ATOM(autocheck, "autocheck")
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -134,16 +134,20 @@ nsScriptLoader::nsScriptLoader(nsIDocume
 nsScriptLoader::~nsScriptLoader()
 {
   mObservers.Clear();
 
   for (PRInt32 i = 0; i < mRequests.Count(); i++) {
     mRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
   }
 
+  for (PRInt32 i = 0; i < mAsyncRequests.Count(); i++) {
+    mAsyncRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
+  }
+
   // Unblock the kids, in case any of them moved to a different document
   // subtree in the meantime and therefore aren't actually going away.
   for (PRUint32 j = 0; j < mPendingChildLoaders.Length(); ++j) {
     mPendingChildLoaders[j]->RemoveExecuteBlocker();
   }  
 }
 
 NS_IMPL_ISUPPORTS1(nsScriptLoader, nsIStreamLoaderObserver)
@@ -496,79 +500,96 @@ nsScriptLoader::ProcessScriptElement(nsI
   nsRefPtr<nsScriptLoadRequest> request;
   if (scriptURI) {
     nsTArray<PreloadInfo>::index_type i =
       mPreloads.IndexOf(scriptURI.get(), 0, PreloadURIComparator());
     if (i != nsTArray<PreloadInfo>::NoIndex) {
       request = mPreloads[i].mRequest;
       request->mElement = aElement;
       request->mJSVersion = version;
-      request->mDefer = mDeferEnabled && aElement->GetScriptDeferred();
+      request->mDefer = mDeferEnabled && aElement->GetScriptDeferred() &&
+        !aElement->GetScriptAsync();
       mPreloads.RemoveElementAt(i);
 
       rv = CheckContentPolicy(mDocument, aElement, request->mURI, type);
       if (NS_FAILED(rv)) {
         // Note, we're dropping our last ref to request here.
         return rv;
       }
 
-      if (!request->mLoading && !request->mDefer && !hadPendingRequests &&
-            ReadyToExecuteScripts() && nsContentUtils::IsSafeToRunScript()) {
+      // Can we run the script now?
+      // This is true if we're done loading, the script isn't deferred and
+      // there are either no scripts or stylesheets to wait for, or the
+      // script is async
+      PRBool readyToRun =
+        !request->mLoading && !request->mDefer &&
+        ((!hadPendingRequests && ReadyToExecuteScripts()) ||
+         aElement->GetScriptAsync());
+
+      if (readyToRun && nsContentUtils::IsSafeToRunScript()) {
         return ProcessRequest(request);
       }
 
       // Not done loading yet. Move into the real requests queue and wait.
-      mRequests.AppendObject(request);
+      if (aElement->GetScriptAsync()) {
+        mAsyncRequests.AppendObject(request);
+      }
+      else {
+        mRequests.AppendObject(request);
+      }
 
-      if (!request->mLoading && !hadPendingRequests && ReadyToExecuteScripts() &&
-          !request->mDefer) {
+      if (readyToRun) {
         nsContentUtils::AddScriptRunner(new nsRunnableMethod<nsScriptLoader>(this,
           &nsScriptLoader::ProcessPendingRequests));
       }
 
-      return request->mDefer ? NS_OK : NS_ERROR_HTMLPARSER_BLOCK;
+      return request->mDefer || aElement->GetScriptAsync() ?
+        NS_OK : NS_ERROR_HTMLPARSER_BLOCK;
     }
   }
 
   // Create a request object for this script
   request = new nsScriptLoadRequest(aElement, version);
   NS_ENSURE_TRUE(request, NS_ERROR_OUT_OF_MEMORY);
 
-  request->mDefer = mDeferEnabled && aElement->GetScriptDeferred();
-
   // First check to see if this is an external script
   if (scriptURI) {
+    request->mDefer = mDeferEnabled && aElement->GetScriptDeferred() &&
+      !aElement->GetScriptAsync();
     request->mURI = scriptURI;
     request->mIsInline = PR_FALSE;
     request->mLoading = PR_TRUE;
 
     rv = StartLoad(request, type);
     if (NS_FAILED(rv)) {
       return rv;
     }
   } else {
+    request->mDefer = PR_FALSE;
     request->mLoading = PR_FALSE;
     request->mIsInline = PR_TRUE;
     request->mURI = mDocument->GetDocumentURI();
 
     request->mLineNo = aElement->GetScriptLineNumber();
 
     // If we've got existing pending requests, add ourselves
     // to this list.
-    if (!request->mDefer && !hadPendingRequests &&
-        ReadyToExecuteScripts() && nsContentUtils::IsSafeToRunScript()) {
+    if (!hadPendingRequests && ReadyToExecuteScripts() &&
+        nsContentUtils::IsSafeToRunScript()) {
       return ProcessRequest(request);
     }
   }
 
   // Add the request to our requests list
-  NS_ENSURE_TRUE(mRequests.AppendObject(request),
+  NS_ENSURE_TRUE(aElement->GetScriptAsync() ?
+                 mAsyncRequests.AppendObject(request) :
+                 mRequests.AppendObject(request),
                  NS_ERROR_OUT_OF_MEMORY);
 
-  if (request->mDefer) {
+  if (request->mDefer || aElement->GetScriptAsync()) {
     return NS_OK;
   }
 
   // If there weren't any pending requests before, and this one is
   // ready to execute, do that as soon as it's safe.
   if (!request->mLoading && !hadPendingRequests && ReadyToExecuteScripts()) {
     nsContentUtils::AddScriptRunner(new nsRunnableMethod<nsScriptLoader>(this,
       &nsScriptLoader::ProcessPendingRequests));
@@ -739,24 +760,34 @@ nsScriptLoader::ProcessPendingRequests()
   nsRefPtr<nsScriptLoadRequest> request;
   while (ReadyToExecuteScripts() &&
          (request = GetFirstPendingRequest()) &&
          !request->mLoading) {
     mRequests.RemoveObject(request);
     ProcessRequest(request);
   }
 
+  // Async scripts don't wait for scriptblockers
+  for (PRInt32 i = 0; mEnabled && i < mAsyncRequests.Count(); ++i) {
+    if (!mAsyncRequests[i]->mLoading) {
+      request = mAsyncRequests[i];
+      mAsyncRequests.RemoveObjectAt(i);
+      ProcessRequest(request);
+      i = 0;
+    }
+  }
+
   while (!mPendingChildLoaders.IsEmpty() && ReadyToExecuteScripts()) {
     nsRefPtr<nsScriptLoader> child = mPendingChildLoaders[0];
     mPendingChildLoaders.RemoveElementAt(0);
     child->RemoveExecuteBlocker();
   }
 
   if (mUnblockOnloadWhenDoneProcessing && mDocument &&
-      !GetFirstPendingRequest()) {
+      !GetFirstPendingRequest() && !mAsyncRequests.Count()) {
     // No more pending scripts; time to unblock onload.
     // OK to unblock onload synchronously here, since callers must be
     // prepared for the world changing anyway.
     mUnblockOnloadWhenDoneProcessing = PR_FALSE;
     mDocument->UnblockOnload(PR_TRUE);
   }
 }
 
@@ -915,20 +946,21 @@ nsScriptLoader::OnStreamComplete(nsIStre
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
   nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
                                      aString);
   if (NS_FAILED(rv)) {
-    if (!mRequests.RemoveObject(request)) {
+    if (mRequests.RemoveObject(request) ||
+        mAsyncRequests.RemoveObject(request)) {
+      FireScriptAvailable(rv, request);
+    } else {
       mPreloads.RemoveElement(request, PreloadRequestComparator());
-    } else {
-      FireScriptAvailable(rv, request);
     }
   }
 
   // Process our request and/or any pending ones
   ProcessPendingRequests();
 
   return NS_OK;
 }
@@ -988,16 +1020,17 @@ nsScriptLoader::PrepareLoadedRequest(nsS
     }
   }
 
   // This assertion could fire errorously if we ran out of memory when
   // inserting the request in the array. However it's an unlikely case
   // so if you see this assertion it is likely something else that is
   // wrong, especially if you see it more than once.
   NS_ASSERTION(mRequests.IndexOf(aRequest) >= 0 ||
+               mAsyncRequests.IndexOf(aRequest) >= 0 ||
                mPreloads.Contains(aRequest, PreloadRequestComparator()),
                "aRequest should be pending!");
 
   // Mark this as loaded
   aRequest->mLoading = PR_FALSE;
 
   return NS_OK;
 }
--- a/content/base/src/nsScriptLoader.h
+++ b/content/base/src/nsScriptLoader.h
@@ -295,16 +295,17 @@ protected:
                                 const PRUint8* aString);
 
   // Returns the first pending (non deferred) request
   nsScriptLoadRequest* GetFirstPendingRequest();
 
   nsIDocument* mDocument;                   // [WEAK]
   nsCOMArray<nsIScriptLoaderObserver> mObservers;
   nsCOMArray<nsScriptLoadRequest> mRequests;
+  nsCOMArray<nsScriptLoadRequest> mAsyncRequests;
 
   // In mRequests, the additional information here is stored by the element.
   struct PreloadInfo {
     nsRefPtr<nsScriptLoadRequest> mRequest;
     nsString mCharset;
   };
 
   struct PreloadRequestComparator {
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -319,16 +319,20 @@ include $(topsrcdir)/config/rules.mk
 		bug466409-empty.css \
 		test_bug466409.html \
 		test_classList.html \
 		test_bug514487.html \
 		test_range_bounds.html \
 		test_bug475156.html \
 		bug475156.sjs \
 		test_copypaste.html \
+		test_bug503481.html \
+		file_bug503481.sjs \
+		test_bug503481b.html \
+		file_bug503481b_inner.html \
 		$(NULL)
 
 # Disabled; see bug 492181
 #		test_plugin_freezing.html
 
 # Disabled for now. Mochitest isn't reliable enough for these.
 # test_bug444546.html \
 # bug444546.sjs \
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_bug503481.sjs
@@ -0,0 +1,32 @@
+function handleRequest(request, response)
+{
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+
+  dump("@@@@@" + request.queryString);
+
+  if (query.unblock) {
+    let blockedResponse = null;
+    try {
+      getObjectState("bug503481_" + query.unblock, function(x) {blockedResponse = x.wrappedJSObject.r});
+    } catch(e) {
+      throw "unable to unblock '" + query.unblock + "': " + e.message;
+    }
+    setObjectState("bug503481_" + query.unblock, null);
+    blockedResponse.finish();
+  }
+
+  if (query.blockOn) {
+    response.processAsync();
+    x = { r: response, QueryInterface: function(iid) { return this } };
+    x.wrappedJSObject = x;
+    setObjectState("bug503481_" + query.blockOn, x);
+  }
+
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/plain", false);
+  response.write(query.body);
+}
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_bug503481b_inner.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!-- Async script that isn't preloaded -->
+<script async src="file_bug503481.sjs?blockOn=R&body=runFirst();"></script>
+<script>
+firstRan = false;
+secondRan = false;
+thirdRan = false;
+forthRan = false;
+fifthRan = false;
+sixthRan = false;
+function runFirst() {
+  firstRan = true;
+}
+function runThird() {
+  parent.is(forthRan, false, "forth should still be blocked");
+  unblock("T");
+  thirdRan = true;
+}
+function runForth() {
+  forthRan = true;
+}
+function runFifth() {
+  parent.is(sixthRan, false, "sixth should be not run before non-async fifth");
+  fifthRan = true;
+}
+function runSixth() {
+  parent.is(fifthRan, true, "fifth should run before async sixth");
+  sixthRan = true;
+}
+
+function done() {
+  parent.is(firstRan, true, "first should have run by onload");
+  parent.is(secondRan, true, "second should have run by onload");
+  parent.is(thirdRan, true, "third should have run by onload");
+  parent.is(forthRan, true, "forth should have run by onload");
+  parent.is(fifthRan, true, "fifth should have run by onload");
+  parent.is(sixthRan, true, "sixth should have run by onload");
+  parent.SimpleTest.finish();
+}
+
+var reqs = [];
+function unblock(s) {
+  xhr = new XMLHttpRequest();
+  xhr.open("GET", "file_bug503481.sjs?unblock=" + s);
+  xhr.send();
+  reqs.push(xhr);
+}
+
+
+parent.is(firstRan, false, "First async script shouldn't have run");
+unblock("R");
+</script>
+
+<!-- test that inline async isn't actually async -->
+<script async>
+secondRan = true;
+</script>
+<script async>
+parent.is(secondRan, true, "Second script shouldn't be async");
+</script>
+
+<!-- test that setting both defer and async treats the script as async -->
+<script defer async src="file_bug503481.sjs?blockOn=S&body=runThird();"></script>
+<script>
+parent.is(thirdRan, false, "third should not have run yet");
+unblock("S");
+</script>
+<script src="file_bug503481.sjs?blockOn=T&body=runForth();"></script>
+
+<!-- test that preloading an async script works -->
+<script src="file_bug503481.sjs?blockOn=U&body=runFifth();"></script>
+<script async src="file_bug503481.sjs?unblock=U&body=runSixth();"></script>
+</head>
+
+<body onload="done()">
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug503481.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=503481
+-->
+<head>
+  <title>Test for Bug 503481</title>
+  <script src="/MochiKit/packed.js"></script>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="done();">
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=503481"
+   target="_blank" >Mozilla Bug 503481</a>
+
+<p id="display"></p>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+function done() {
+  is(firstRan, true, "first has run");
+  is(secondRan, true, "second has run");
+  is(thirdRan, true, "third has run");
+  SimpleTest.finish();
+}
+var reqs = [];
+function unblock(s) {
+  xhr = new XMLHttpRequest();
+  xhr.open("GET", "file_bug503481.sjs?unblock=" + s);
+  xhr.send();
+  reqs.push(xhr);
+}
+var firstRan = false, secondRan = false, thirdRan = false;
+function runFirst() { firstRan = true; }
+function runSecond() {
+  is(thirdRan, true, "should have run third already");
+  secondRan = true;
+}
+function runThird() {
+  is(secondRan, false, "shouldn't have unblocked second yet");
+  thirdRan = true;
+  unblock("B");
+}
+</script>
+<script id=firstScript async src="file_bug503481.sjs?blockOn=A&body=runFirst();"></script>
+<script id=firstScriptHelper>
+is(document.getElementById("firstScript").async, true,
+   "async set");
+is(document.getElementById("firstScriptHelper").async, false,
+   "async not set");
+document.getElementById("firstScript").async = false;
+is(document.getElementById("firstScript").async, false,
+   "async no longer set");
+is(document.getElementById("firstScript").hasAttribute("async"), false,
+   "async attribute no longer set");
+is(firstRan, false, "First async script shouldn't have run");
+unblock("A");
+</script>
+
+<script async src="file_bug503481.sjs?blockOn=B&body=runSecond();"></script>
+<script async src="file_bug503481.sjs?blockOn=C&body=runThird();"></script>
+<script>
+is(secondRan, false, "Second async script shouldn't have run");
+is(thirdRan, false, "Third async script shouldn't have run");
+unblock("C");
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug503481b.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=503481
+-->
+<head>
+  <title>Test for Bug 503481</title>
+  <script src="/MochiKit/packed.js"></script>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=503481"
+   target="_blank" >Mozilla Bug 503481</a>
+
+<iframe src="file_bug503481b_inner.html"></iframe>
+<script>
+SimpleTest.waitForExplicitFinish();
+// script in the iframe will call SimpleTest.finish()
+</script>
+</body>
+</html>
--- a/content/html/content/src/nsHTMLScriptElement.cpp
+++ b/content/html/content/src/nsHTMLScriptElement.cpp
@@ -31,16 +31,17 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "nsIDOMHTMLScriptElement.h"
+#include "nsIDOMNSHTMLScriptElement.h"
 #include "nsIDOMEventTarget.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsStyleConsts.h"
 #include "nsIDocument.h"
 #include "nsScriptElement.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
@@ -299,16 +300,17 @@ nsHTMLScriptEventHandler::Invoke(nsISupp
   nsCOMPtr<nsIVariant> ret;
   return scriptContext->CallEventHandler(aTargetObject, scope, funcObject,
                                          argarray, getter_AddRefs(ret));
 }
 
 
 class nsHTMLScriptElement : public nsGenericHTMLElement,
                             public nsIDOMHTMLScriptElement,
+                            public nsIDOMNSHTMLScriptElement,
                             public nsScriptElement
 {
 public:
   nsHTMLScriptElement(nsINodeInfo *aNodeInfo, PRBool aFromParser);
   virtual ~nsHTMLScriptElement();
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
@@ -317,25 +319,26 @@ public:
   NS_FORWARD_NSIDOMNODE(nsGenericHTMLElement::)
 
   // nsIDOMElement
   NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLElement::)
 
   // nsIDOMHTMLElement
   NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::)
 
-  // nsIDOMHTMLScriptElement
   NS_DECL_NSIDOMHTMLSCRIPTELEMENT
+  NS_DECL_NSIDOMNSHTMLSCRIPTELEMENT
 
   // nsIScriptElement
   virtual void GetScriptType(nsAString& type);
   virtual already_AddRefed<nsIURI> GetScriptURI();
   virtual void GetScriptText(nsAString& text);
   virtual void GetScriptCharset(nsAString& charset);
   virtual PRBool GetScriptDeferred();
+  virtual PRBool GetScriptAsync();
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               PRBool aCompileEventHandlers);
 
   virtual nsresult GetInnerHTML(nsAString& aInnerHTML);
   virtual nsresult SetInnerHTML(const nsAString& aInnerHTML);
@@ -373,20 +376,21 @@ nsHTMLScriptElement::~nsHTMLScriptElemen
 }
 
 
 NS_IMPL_ADDREF_INHERITED(nsHTMLScriptElement, nsGenericElement)
 NS_IMPL_RELEASE_INHERITED(nsHTMLScriptElement, nsGenericElement)
 
 // QueryInterface implementation for nsHTMLScriptElement
 NS_INTERFACE_TABLE_HEAD(nsHTMLScriptElement)
-  NS_HTML_CONTENT_INTERFACE_TABLE4(nsHTMLScriptElement,
+  NS_HTML_CONTENT_INTERFACE_TABLE5(nsHTMLScriptElement,
                                    nsIDOMHTMLScriptElement,
                                    nsIScriptLoaderObserver,
                                    nsIScriptElement,
+                                   nsIDOMNSHTMLScriptElement,
                                    nsIMutationObserver)
   NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLScriptElement,
                                                nsGenericHTMLElement)
   if (mScriptEventHandler && aIID.Equals(NS_GET_IID(nsIScriptEventHandler)))
     foundInterface = static_cast<nsIScriptEventHandler*>
                                 (mScriptEventHandler);
   else
   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(HTMLScriptElement)
@@ -445,16 +449,17 @@ NS_IMETHODIMP
 nsHTMLScriptElement::SetText(const nsAString& aValue)
 {
   return nsContentUtils::SetNodeTextContent(this, aValue, PR_TRUE);
 }
 
 
 NS_IMPL_STRING_ATTR(nsHTMLScriptElement, Charset, charset)
 NS_IMPL_BOOL_ATTR(nsHTMLScriptElement, Defer, defer)
+NS_IMPL_BOOL_ATTR(nsHTMLScriptElement, Async, async)
 NS_IMPL_URI_ATTR(nsHTMLScriptElement, Src, src)
 NS_IMPL_STRING_ATTR(nsHTMLScriptElement, Type, type)
 NS_IMPL_STRING_ATTR(nsHTMLScriptElement, HtmlFor, _for)
 NS_IMPL_STRING_ATTR(nsHTMLScriptElement, Event, event)
 
 nsresult
 nsHTMLScriptElement::GetInnerHTML(nsAString& aInnerHTML)
 {
@@ -514,21 +519,32 @@ void
 nsHTMLScriptElement::GetScriptCharset(nsAString& charset)
 {
   GetCharset(charset);
 }
 
 PRBool
 nsHTMLScriptElement::GetScriptDeferred()
 {
-  PRBool defer;
+  PRBool defer, async;
+  GetAsync(&async);
   GetDefer(&defer);
   nsCOMPtr<nsIURI> uri = GetScriptURI();
 
-  return defer && uri;
+  return !async && defer && uri;
+}
+
+PRBool
+nsHTMLScriptElement::GetScriptAsync()
+{
+  PRBool async;
+  GetAsync(&async);
+  nsCOMPtr<nsIURI> uri = GetScriptURI();
+
+  return async && uri;
 }
 
 PRBool
 nsHTMLScriptElement::HasScriptContent()
 {
   return HasAttr(kNameSpaceID_None, nsGkAtoms::src) ||
          nsContentUtils::HasNonEmptyTextContent(this);
 }
--- a/content/svg/content/src/nsSVGScriptElement.cpp
+++ b/content/svg/content/src/nsSVGScriptElement.cpp
@@ -75,16 +75,17 @@ public:
   NS_FORWARD_NSIDOMSVGELEMENT(nsSVGScriptElementBase::)
 
   // nsIScriptElement
   virtual void GetScriptType(nsAString& type);
   virtual already_AddRefed<nsIURI> GetScriptURI();
   virtual void GetScriptText(nsAString& text);
   virtual void GetScriptCharset(nsAString& charset);
   virtual PRBool GetScriptDeferred();
+  virtual PRBool GetScriptAsync();
 
   // nsScriptElement
   virtual PRBool HasScriptContent();
 
   // nsSVGElement specializations:
   virtual void DidChangeString(PRUint8 aAttrEnum);
 
   // nsIContent specializations:
@@ -227,16 +228,22 @@ nsSVGScriptElement::GetScriptCharset(nsA
 }
 
 PRBool
 nsSVGScriptElement::GetScriptDeferred()
 {
   return PR_FALSE;
 }
 
+PRBool
+nsSVGScriptElement::GetScriptAsync()
+{
+  return PR_FALSE;
+}
+
 //----------------------------------------------------------------------
 // nsScriptElement methods
 
 PRBool
 nsSVGScriptElement::HasScriptContent()
 {
   nsAutoString str;
   mStringAttributes[HREF].GetAnimValue(str, this);
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -288,16 +288,17 @@
 #include "nsIDOMHTMLOListElement.h"
 #include "nsIDOMHTMLObjectElement.h"
 #include "nsIDOMHTMLOptGroupElement.h"
 #include "nsIDOMHTMLParagraphElement.h"
 #include "nsIDOMHTMLParamElement.h"
 #include "nsIDOMHTMLPreElement.h"
 #include "nsIDOMHTMLQuoteElement.h"
 #include "nsIDOMHTMLScriptElement.h"
+#include "nsIDOMNSHTMLScriptElement.h"
 #include "nsIDOMNSHTMLSelectElement.h"
 #include "nsIDOMHTMLStyleElement.h"
 #include "nsIDOMHTMLTableCaptionElem.h"
 #include "nsIDOMHTMLTableCellElement.h"
 #include "nsIDOMHTMLTableColElement.h"
 #include "nsIDOMHTMLTableElement.h"
 #include "nsIDOMHTMLTableRowElement.h"
 #include "nsIDOMHTMLTableSectionElem.h"
@@ -2507,16 +2508,17 @@ nsDOMClassInfo::Init()
 
   DOM_CLASSINFO_MAP_BEGIN(HTMLQuoteElement, nsIDOMHTMLQuoteElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLQuoteElement)
     DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(HTMLScriptElement, nsIDOMHTMLScriptElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLScriptElement)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSHTMLScriptElement)
     DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(HTMLSelectElement, nsIDOMHTMLSelectElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLSelectElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSHTMLSelectElement)
     DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
--- a/dom/interfaces/html/Makefile.in
+++ b/dom/interfaces/html/Makefile.in
@@ -128,11 +128,12 @@ XPIDLSRCS = 					\
 	nsIDOMNSHTMLFrameElement.idl		\
 	nsIDOMNSHTMLHRElement.idl		\
 	nsIDOMNSHTMLImageElement.idl		\
 	nsIDOMNSHTMLInputElement.idl		\
 	nsIDOMNSHTMLOptionCollectn.idl		\
 	nsIDOMNSHTMLOptionElement.idl		\
 	nsIDOMNSHTMLSelectElement.idl		\
 	nsIDOMNSHTMLTextAreaElement.idl		\
+	nsIDOMNSHTMLScriptElement.idl		\
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/html/nsIDOMNSHTMLScriptElement.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "domstubs.idl"
+
+[scriptable, uuid(5b2065d7-7888-4529-8a29-e58390a40bd2)]
+interface nsIDOMNSHTMLScriptElement : nsISupports
+{
+	attribute boolean async;
+};