Bug 688580 - Run deferred scripts before DOMContentLoaded; r=hsivonen

This commit is contained in:
Olli Pettay 2014-04-12 11:02:21 +02:00
parent ebe60f449b
commit 736b1d9117
13 changed files with 210 additions and 14 deletions

View File

@ -127,8 +127,8 @@ typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
} // namespace mozilla
#define NS_IDOCUMENT_IID \
{ 0xa7679e4a, 0xa5ec, 0x45bf, \
{ 0x8f, 0xe4, 0xad, 0x4a, 0xb8, 0xc7, 0x7f, 0xc7 } }
{ 0x906d05e7, 0x39af, 0x4ff0, \
{ 0xbc, 0xcd, 0x30, 0x0c, 0x7f, 0xeb, 0x86, 0x21 } }
// Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
@ -1336,6 +1336,13 @@ public:
*/
virtual void UnblockOnload(bool aFireSync) = 0;
void BlockDOMContentLoaded()
{
++mBlockDOMContentLoaded;
}
virtual void UnblockDOMContentLoaded() = 0;
/**
* Notification that the page has been shown, for documents which are loaded
* into a DOM window. This corresponds to the completion of document load,
@ -2554,6 +2561,9 @@ protected:
uint32_t mInSyncOperationCount;
nsRefPtr<mozilla::dom::XPathEvaluator> mXPathEvaluator;
uint32_t mBlockDOMContentLoaded;
bool mDidFireDOMContentLoaded:1;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID)

View File

@ -1527,7 +1527,8 @@ nsIDocument::nsIDocument()
mAllowDNSPrefetch(true),
mIsBeingUsedAsImage(false),
mHasLinksToUpdate(false),
mPartID(0)
mPartID(0),
mDidFireDOMContentLoaded(true)
{
SetInDocument();
}
@ -4679,6 +4680,8 @@ nsDocument::BeginLoad()
// Block onload here to prevent having to deal with blocking and
// unblocking it while we know the document is loading.
BlockOnload();
mDidFireDOMContentLoaded = false;
BlockDOMContentLoaded();
if (mScriptLoader) {
mScriptLoader->BeginDeferringScripts();
@ -4920,6 +4923,19 @@ nsDocument::EndLoad()
NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
UnblockDOMContentLoaded();
}
void
nsDocument::UnblockDOMContentLoaded()
{
MOZ_ASSERT(mBlockDOMContentLoaded);
if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
return;
}
mDidFireDOMContentLoaded = true;
MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
if (!mSynchronousDOMContentLoaded) {
nsRefPtr<nsIRunnable> ev =
NS_NewRunnableMethod(this, &nsDocument::DispatchContentLoadedEvents);

View File

@ -1244,6 +1244,8 @@ public:
mozilla::ErrorResult& rv) MOZ_OVERRIDE;
virtual void UseRegistryFromDocument(nsIDocument* aDocument) MOZ_OVERRIDE;
virtual void UnblockDOMContentLoaded() MOZ_OVERRIDE;
protected:
friend class nsNodeUtils;
friend class nsDocumentOnStack;

View File

@ -124,7 +124,8 @@ nsScriptLoader::nsScriptLoader(nsIDocument *aDocument)
mBlockerCount(0),
mEnabled(true),
mDeferEnabled(false),
mDocumentParsingDone(false)
mDocumentParsingDone(false),
mBlockingDOMContentLoaded(false)
{
// enable logging for CSP
#ifdef PR_LOGGING
@ -656,7 +657,7 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
NS_ASSERTION(mDocument->GetCurrentContentSink() ||
aElement->GetParserCreated() == FROM_PARSER_XSLT,
"Non-XSLT Defer script on a document without an active parser; bug 592366.");
mDeferRequests.AppendElement(request);
AddDeferRequest(request);
return false;
}
@ -1171,6 +1172,9 @@ nsScriptLoader::ProcessPendingRequests()
!mParserBlockingRequest && mAsyncRequests.IsEmpty() &&
mNonAsyncExternalScriptInsertedRequests.IsEmpty() &&
mXSLTRequests.IsEmpty() && mDeferRequests.IsEmpty()) {
if (MaybeRemovedDeferRequests()) {
return ProcessPendingRequests();
}
// No more pending scripts; time to unblock onload.
// OK to unblock onload synchronously here, since callers must be
// prepared for the world changing anyway.
@ -1488,3 +1492,27 @@ nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
pi->mRequest = request;
pi->mCharset = aCharset;
}
void
nsScriptLoader::AddDeferRequest(nsScriptLoadRequest* aRequest)
{
mDeferRequests.AppendElement(aRequest);
if (mDeferEnabled && mDeferRequests.Length() == 1 && mDocument &&
!mBlockingDOMContentLoaded) {
MOZ_ASSERT(mDocument->GetReadyStateEnum() == nsIDocument::READYSTATE_LOADING);
mBlockingDOMContentLoaded = true;
mDocument->BlockDOMContentLoaded();
}
}
bool
nsScriptLoader::MaybeRemovedDeferRequests()
{
if (mDeferRequests.Length() == 0 && mDocument &&
mBlockingDOMContentLoaded) {
mBlockingDOMContentLoaded = false;
mDocument->UnblockDOMContentLoaded();
return true;
}
return false;
}

View File

@ -291,6 +291,9 @@ private:
uint32_t aStringLen,
const uint8_t* aString);
void AddDeferRequest(nsScriptLoadRequest* aRequest);
bool MaybeRemovedDeferRequests();
nsIDocument* mDocument; // [WEAK]
nsCOMArray<nsIScriptLoaderObserver> mObservers;
nsTArray<nsRefPtr<nsScriptLoadRequest> > mNonAsyncExternalScriptInsertedRequests;
@ -325,6 +328,7 @@ private:
bool mEnabled;
bool mDeferEnabled;
bool mDocumentParsingDone;
bool mBlockingDOMContentLoaded;
};
class nsAutoScriptLoaderDisabler

View File

@ -21,7 +21,7 @@ load 473284.xul
load 499006-1.html
load 499006-2.html
load 502617.html
asserts(1-2) load 504224.html # bug 564098
load 504224.html
load 603531.html
load 601247.html
load 609560-1.xhtml

View File

@ -93,6 +93,6 @@ support-files =
[test_frameWrapping.html]
# The JS test component we use below is only available in debug builds.
[test_getWebIDLCaller.html]
skip-if = debug == false
skip-if = (debug == false || os == "android")
[test_nac.xhtml]
[test_sameOriginPolicy.html]

View File

@ -95,14 +95,14 @@ nsHtml5TreeOpExecutor::WillParse()
NS_IMETHODIMP
nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode)
{
mDocument->AddObserver(this);
WillBuildModelImpl();
GetDocument()->BeginLoad();
if (mDocShell && !GetDocument()->GetWindow() &&
!IsExternalViewSource()) {
// Not loading as data but script global object not ready
return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR);
}
mDocument->AddObserver(this);
WillBuildModelImpl();
GetDocument()->BeginLoad();
return NS_OK;
}
@ -111,8 +111,6 @@ nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode)
NS_IMETHODIMP
nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated)
{
NS_PRECONDITION(mStarted, "Bad life cycle.");
if (!aTerminated) {
// This is needed to avoid unblocking loads too many times on one hand
// and on the other hand to avoid destroying the frame constructor from
@ -162,7 +160,12 @@ nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated)
// Return early to avoid unblocking the onload event too many times.
return NS_OK;
}
mDocument->EndLoad();
// We may not have called BeginLoad() if loading is terminated before
// OnStartRequest call.
if (mStarted) {
mDocument->EndLoad();
}
DropParserAndPerfHint();
#ifdef GATHER_DOCWRITE_STATISTICS
printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);

View File

@ -0,0 +1,4 @@
is(document.readyState, "interactive", "readyState should be interactive during defer.");
is(state, "readyState interactive", "Bad state upon defer");
state = "defer";

View File

@ -25,6 +25,7 @@ support-files =
file_bug672453_meta_restart.html
file_bug672453_meta_unsupported.html
file_bug672453_meta_utf16.html
file_bug688580.js
file_bug672453_not_declared.html
file_bug672453_meta_userdefined.html
file_bug716579-16.html
@ -116,6 +117,8 @@ support-files =
[test_bug655682.html]
[test_bug667533.html]
[test_bug672453.html]
[test_bug688580.html]
[test_bug688580.xhtml]
[test_bug709083.html]
[test_bug715112.html]
[test_bug715739.html]

View File

@ -0,0 +1,64 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=688580
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 688580</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
/** Test for Bug 688580 **/
// Expected order:
// Test starting
// readyState interactive
// defer
// DOMContentLoaded
// readyState complete
// load
var state = "Test starting";
var readyStateCall = 0;
SimpleTest.waitForExplicitFinish();
is(document.readyState, "loading", "Document should have been loading.");
document.addEventListener("DOMContentLoaded", function () {
is(document.readyState, "interactive", "readyState should be interactive during DOMContentLoaded.");
is(state, "defer", "Bad state upon DOMContentLoaded");
state = "DOMContentLoaded";
});
document.addEventListener("readystatechange", function () {
readyStateCall++;
if (readyStateCall == 1) {
is(document.readyState, "interactive", "readyState should have changed to interactive.");
is(state, "Test starting", "Bad state upon first readystatechange.");
state = "readyState interactive";
} else if (readyStateCall == 2) {
is(document.readyState, "complete", "readyState should have changed to complete.");
is(state, "DOMContentLoaded", "Bad state upon second readystatechange.");
state = "readyState complete";
} else {
ok(false, "Too many readystatechanges");
}
});
window.addEventListener("load", function () {
is(document.readyState, "complete", "readyState should be complete during load.");
is(state, "readyState complete", "Bad state upon load")
state = "load";
SimpleTest.finish();
});
</script>
<script defer src="file_bug688580.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=688580">Mozilla Bug 688580</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -0,0 +1,62 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=688580
-->
<head>
<title>Test for Bug 688580</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
/** Test for Bug 688580 **/
// Expected order:
// Test starting
// readyState interactive
// defer
// DOMContentLoaded
// readyState complete
// load
var state = "Test starting";
var readyStateCall = 0;
SimpleTest.waitForExplicitFinish();
is(document.readyState, "loading", "Document should have been loading.");
document.addEventListener("DOMContentLoaded", function () {
is(document.readyState, "interactive", "readyState should be interactive during DOMContentLoaded.");
is(state, "defer", "Bad state upon DOMContentLoaded");
state = "DOMContentLoaded";
});
document.addEventListener("readystatechange", function () {
readyStateCall++;
if (readyStateCall == 1) {
is(document.readyState, "interactive", "readyState should have changed to interactive.");
is(state, "Test starting", "Bad state upon first readystatechange.");
state = "readyState interactive";
} else if (readyStateCall == 2) {
is(document.readyState, "complete", "readyState should have changed to complete.");
is(state, "DOMContentLoaded", "Bad state upon second readystatechange.");
state = "readyState complete";
} else {
ok(false, "Too many readystatechanges");
}
});
window.addEventListener("load", function () {
is(document.readyState, "complete", "readyState should be complete during load.");
is(state, "readyState complete", "Bad state upon load")
state = "load";
SimpleTest.finish();
});
</script>
<script defer="" src="file_bug688580.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=688580">Mozilla Bug 688580</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -26,7 +26,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=762993
"use strict";
SimpleTest.expectAssertions(2);
SimpleTest.expectAssertions(1);
SimpleTest.waitForExplicitFinish();