Bug 503481: Implement async attribute

This commit is contained in:
Jonas Sicking 2009-11-09 17:04:24 -08:00
parent 546e09050f
commit 8f545535ea
14 changed files with 340 additions and 23 deletions

View File

@ -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;

View File

@ -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")

View File

@ -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<nsScriptLoader>(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<nsScriptLoader> 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!");

View File

@ -300,6 +300,7 @@ protected:
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 {

View File

@ -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

View File

@ -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);
}

View File

@ -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()">

View File

@ -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>

View File

@ -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>

View File

@ -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<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

View File

@ -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

View File

@ -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

View File

@ -133,6 +133,7 @@ XPIDLSRCS = \
nsIDOMNSHTMLOptionElement.idl \
nsIDOMNSHTMLSelectElement.idl \
nsIDOMNSHTMLTextAreaElement.idl \
nsIDOMNSHTMLScriptElement.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -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;
};