From 8f545535ea6726f92971ad3ceccb2be6b9fd1561 Mon Sep 17 00:00:00 2001 From: Jonas Sicking Date: Mon, 9 Nov 2009 17:04:24 -0800 Subject: [PATCH] Bug 503481: Implement async attribute --- content/base/public/nsIScriptElement.h | 10 ++- content/base/src/nsGkAtomList.h | 1 + content/base/src/nsScriptLoader.cpp | 67 ++++++++++++---- content/base/src/nsScriptLoader.h | 1 + content/base/test/Makefile.in | 4 + content/base/test/file_bug503481.sjs | 32 ++++++++ content/base/test/file_bug503481b_inner.html | 77 +++++++++++++++++++ content/base/test/test_bug503481.html | 70 +++++++++++++++++ content/base/test/test_bug503481b.html | 23 ++++++ .../html/content/src/nsHTMLScriptElement.cpp | 24 +++++- .../svg/content/src/nsSVGScriptElement.cpp | 7 ++ dom/base/nsDOMClassInfo.cpp | 2 + dom/interfaces/html/Makefile.in | 1 + .../html/nsIDOMNSHTMLScriptElement.idl | 44 +++++++++++ 14 files changed, 340 insertions(+), 23 deletions(-) create mode 100644 content/base/test/file_bug503481.sjs create mode 100644 content/base/test/file_bug503481b_inner.html create mode 100644 content/base/test/test_bug503481.html create mode 100644 content/base/test/test_bug503481b.html create mode 100644 dom/interfaces/html/nsIDOMNSHTMLScriptElement.idl diff --git a/content/base/public/nsIScriptElement.h b/content/base/public/nsIScriptElement.h index ca7fc30040c6..d6bacbc65b9c 100644 --- a/content/base/public/nsIScriptElement.h +++ b/content/base/public/nsIScriptElement.h @@ -44,9 +44,10 @@ #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 @@ -87,6 +88,11 @@ public: */ virtual PRBool GetScriptDeferred() = 0; + /** + * Is the script async. Currently only supported by HTML scripts. + */ + virtual PRBool GetScriptAsync() = 0; + void SetScriptLineNumber(PRUint32 aLineNumber) { mLineNumber = aLineNumber; diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h index 438ba50526e9..1b89b56c6e93 100644 --- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -111,6 +111,7 @@ 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") diff --git a/content/base/src/nsScriptLoader.cpp b/content/base/src/nsScriptLoader.cpp index 0d60e8f7f0c7..8af26dae727a 100644 --- a/content/base/src/nsScriptLoader.cpp +++ b/content/base/src/nsScriptLoader.cpp @@ -139,6 +139,10 @@ nsScriptLoader::~nsScriptLoader() 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) { @@ -501,7 +505,8 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement) 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); @@ -510,21 +515,34 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement) 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(this, &nsScriptLoader::ProcessPendingRequests)); } - return request->mDefer ? NS_OK : NS_ERROR_HTMLPARSER_BLOCK; + return request->mDefer || aElement->GetScriptAsync() ? + NS_OK : NS_ERROR_HTMLPARSER_BLOCK; } } @@ -532,10 +550,10 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement) 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; @@ -545,6 +563,7 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement) return rv; } } else { + request->mDefer = PR_FALSE; request->mLoading = PR_FALSE; request->mIsInline = PR_TRUE; request->mURI = mDocument->GetDocumentURI(); @@ -553,17 +572,19 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement) // 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; } @@ -744,6 +765,16 @@ nsScriptLoader::ProcessPendingRequests() 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 child = mPendingChildLoaders[0]; mPendingChildLoaders.RemoveElementAt(0); @@ -751,7 +782,7 @@ nsScriptLoader::ProcessPendingRequests() } 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. @@ -920,10 +951,11 @@ nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, aString); if (NS_FAILED(rv)) { - if (!mRequests.RemoveObject(request)) { - mPreloads.RemoveElement(request, PreloadRequestComparator()); - } else { + if (mRequests.RemoveObject(request) || + mAsyncRequests.RemoveObject(request)) { FireScriptAvailable(rv, request); + } else { + mPreloads.RemoveElement(request, PreloadRequestComparator()); } } @@ -993,6 +1025,7 @@ nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest, // 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!"); diff --git a/content/base/src/nsScriptLoader.h b/content/base/src/nsScriptLoader.h index 13cdd317b9bf..81a32ab9a6ca 100644 --- a/content/base/src/nsScriptLoader.h +++ b/content/base/src/nsScriptLoader.h @@ -300,6 +300,7 @@ protected: nsIDocument* mDocument; // [WEAK] nsCOMArray mObservers; nsCOMArray mRequests; + nsCOMArray mAsyncRequests; // In mRequests, the additional information here is stored by the element. struct PreloadInfo { diff --git a/content/base/test/Makefile.in b/content/base/test/Makefile.in index 8f19cf77252a..a81a47053153 100644 --- a/content/base/test/Makefile.in +++ b/content/base/test/Makefile.in @@ -324,6 +324,10 @@ _TEST_FILES = test_bug5141.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 diff --git a/content/base/test/file_bug503481.sjs b/content/base/test/file_bug503481.sjs new file mode 100644 index 000000000000..e127229d5dc5 --- /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); +} diff --git a/content/base/test/file_bug503481b_inner.html b/content/base/test/file_bug503481b_inner.html new file mode 100644 index 000000000000..0073b3dca023 --- /dev/null +++ b/content/base/test/file_bug503481b_inner.html @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/base/test/test_bug503481.html b/content/base/test/test_bug503481.html new file mode 100644 index 000000000000..94727622d438 --- /dev/null +++ b/content/base/test/test_bug503481.html @@ -0,0 +1,70 @@ + + + + + Test for Bug 503481 + + + + + + +Mozilla Bug 503481 + +

+ + + + + + + + + + + diff --git a/content/base/test/test_bug503481b.html b/content/base/test/test_bug503481b.html new file mode 100644 index 000000000000..d3598bb84291 --- /dev/null +++ b/content/base/test/test_bug503481b.html @@ -0,0 +1,23 @@ + + + + + Test for Bug 503481 + + + + + + +Mozilla Bug 503481 + + + + + diff --git a/content/html/content/src/nsHTMLScriptElement.cpp b/content/html/content/src/nsHTMLScriptElement.cpp index 56adaf0d3302..d4d381ce6f16 100644 --- a/content/html/content/src/nsHTMLScriptElement.cpp +++ b/content/html/content/src/nsHTMLScriptElement.cpp @@ -36,6 +36,7 @@ * * ***** END LICENSE BLOCK ***** */ #include "nsIDOMHTMLScriptElement.h" +#include "nsIDOMNSHTMLScriptElement.h" #include "nsIDOMEventTarget.h" #include "nsGenericHTMLElement.h" #include "nsGkAtoms.h" @@ -304,6 +305,7 @@ nsHTMLScriptEventHandler::Invoke(nsISupports *aTargetObject, class nsHTMLScriptElement : public nsGenericHTMLElement, public nsIDOMHTMLScriptElement, + public nsIDOMNSHTMLScriptElement, public nsScriptElement { public: @@ -322,8 +324,8 @@ public: // nsIDOMHTMLElement NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::) - // nsIDOMHTMLScriptElement NS_DECL_NSIDOMHTMLSCRIPTELEMENT + NS_DECL_NSIDOMNSHTMLSCRIPTELEMENT // nsIScriptElement virtual void GetScriptType(nsAString& type); @@ -331,6 +333,7 @@ public: virtual void GetScriptText(nsAString& text); virtual void GetScriptCharset(nsAString& charset); virtual PRBool GetScriptDeferred(); + virtual PRBool GetScriptAsync(); // nsIContent virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, @@ -378,10 +381,11 @@ 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) @@ -450,6 +454,7 @@ nsHTMLScriptElement::SetText(const nsAString& aValue) 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) @@ -519,11 +524,22 @@ nsHTMLScriptElement::GetScriptCharset(nsAString& charset) PRBool nsHTMLScriptElement::GetScriptDeferred() { - PRBool defer; + PRBool defer, async; + GetAsync(&async); GetDefer(&defer); nsCOMPtr uri = GetScriptURI(); - return defer && uri; + return !async && defer && uri; +} + +PRBool +nsHTMLScriptElement::GetScriptAsync() +{ + PRBool async; + GetAsync(&async); + nsCOMPtr uri = GetScriptURI(); + + return async && uri; } PRBool diff --git a/content/svg/content/src/nsSVGScriptElement.cpp b/content/svg/content/src/nsSVGScriptElement.cpp index d20cb247137c..f6a51bcf996c 100644 --- a/content/svg/content/src/nsSVGScriptElement.cpp +++ b/content/svg/content/src/nsSVGScriptElement.cpp @@ -80,6 +80,7 @@ public: virtual void GetScriptText(nsAString& text); virtual void GetScriptCharset(nsAString& charset); virtual PRBool GetScriptDeferred(); + virtual PRBool GetScriptAsync(); // nsScriptElement virtual PRBool HasScriptContent(); @@ -232,6 +233,12 @@ nsSVGScriptElement::GetScriptDeferred() return PR_FALSE; } +PRBool +nsSVGScriptElement::GetScriptAsync() +{ + return PR_FALSE; +} + //---------------------------------------------------------------------- // nsScriptElement methods diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 9213c014179d..6d75fad4f1b0 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -293,6 +293,7 @@ #include "nsIDOMHTMLPreElement.h" #include "nsIDOMHTMLQuoteElement.h" #include "nsIDOMHTMLScriptElement.h" +#include "nsIDOMNSHTMLScriptElement.h" #include "nsIDOMNSHTMLSelectElement.h" #include "nsIDOMHTMLStyleElement.h" #include "nsIDOMHTMLTableCaptionElem.h" @@ -2512,6 +2513,7 @@ nsDOMClassInfo::Init() 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 diff --git a/dom/interfaces/html/Makefile.in b/dom/interfaces/html/Makefile.in index ff0518dd84e1..e86ba4e2e5bd 100644 --- a/dom/interfaces/html/Makefile.in +++ b/dom/interfaces/html/Makefile.in @@ -133,6 +133,7 @@ XPIDLSRCS = \ nsIDOMNSHTMLOptionElement.idl \ nsIDOMNSHTMLSelectElement.idl \ nsIDOMNSHTMLTextAreaElement.idl \ + nsIDOMNSHTMLScriptElement.idl \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/dom/interfaces/html/nsIDOMNSHTMLScriptElement.idl b/dom/interfaces/html/nsIDOMNSHTMLScriptElement.idl new file mode 100644 index 000000000000..5a597ea9f83a --- /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; +};