Bug 1053321 - Correctly pass info about 'defer' and 'async' attributes to HTML5 parser invoked script preload. r=bkelly

This commit is contained in:
Honza Bambas 2017-08-01 12:43:00 -04:00
parent 43fe35a023
commit d8659dd692
13 changed files with 148 additions and 18 deletions

View File

@ -52,6 +52,8 @@ ScriptLoadRequest::ScriptLoadRequest(ScriptKind aKind,
, mHasSourceMapURL(false)
, mIsDefer(false)
, mIsAsync(false)
, mPreloadAsAsync(false)
, mPreloadAsDefer(false)
, mIsNonAsyncScriptInserted(false)
, mIsXSLT(false)
, mIsCanceled(false)

View File

@ -155,6 +155,8 @@ public:
bool mHasSourceMapURL; // Does the HTTP header have a source map url?
bool mIsDefer; // True if we live in mDeferRequests.
bool mIsAsync; // True if we live in mLoadingAsyncRequests or mLoadedAsyncRequests.
bool mPreloadAsAsync; // True if this is a preload request and the script is async
bool mPreloadAsDefer; // True if this is a preload request and the script is defer
bool mIsNonAsyncScriptInserted; // True if we live in mNonAsyncExternalScriptInsertedRequests
bool mIsXSLT; // True if we live in mXSLTRequests.
bool mIsCanceled; // True if we have been explicitly canceled.

View File

@ -972,15 +972,16 @@ ScriptLoader::StartLoad(ScriptLoadRequest* aRequest)
}
nsIScriptElement* script = aRequest->mElement;
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
bool async = script ? script->GetScriptAsync() : aRequest->mPreloadAsAsync;
bool defer = script ? script->GetScriptDeferred() : aRequest->mPreloadAsDefer;
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
if (cos) {
if (aRequest->mScriptFromHead &&
!(script && (script->GetScriptAsync() || script->GetScriptDeferred()))) {
if (aRequest->mScriptFromHead && !async && !defer) {
// synchronous head scripts block loading of most other non js/css
// content such as images
cos->AddClassFlags(nsIClassOfService::Leader);
} else if (!(script && script->GetScriptDeferred())) {
} else if (!defer) {
// other scripts are neither blocked nor prioritized unless marked deferred
cos->AddClassFlags(nsIClassOfService::Unblocked);
}
@ -2925,7 +2926,7 @@ ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset,
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead,
bool aScriptFromHead, bool aAsync, bool aDefer,
const mozilla::net::ReferrerPolicy aReferrerPolicy)
{
NS_ENSURE_TRUE_VOID(mDocument);
@ -2958,6 +2959,8 @@ ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset,
request->mIsInline = false;
request->mReferrerPolicy = aReferrerPolicy;
request->mScriptFromHead = aScriptFromHead;
request->mPreloadAsAsync = aAsync;
request->mPreloadAsDefer = aDefer;
nsresult rv = StartLoad(request);
if (NS_FAILED(rv)) {

View File

@ -300,7 +300,7 @@ public:
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead,
bool aScriptFromHead, bool aAsync, bool aDefer,
const mozilla::net::ReferrerPolicy aReferrerPolicy);
/**

View File

@ -42,4 +42,8 @@ LOCAL_INCLUDES += [
include('/ipc/chromium/chromium-config.mozbuild')
MOCHITEST_MANIFESTS += [
'test/mochitest.ini',
]
FINAL_LIBRARY = 'xul'

View File

@ -0,0 +1,50 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function setGlobalState(data, key)
{
x = { data: data, QueryInterface: function(iid) { return this } };
x.wrappedJSObject = x;
setObjectState(key, x);
}
function getGlobalState(key)
{
var data;
getObjectState(key, function(x) {
data = x.wrappedJSObject.data;
});
return data;
}
function handleRequest(request, response)
{
var query = request.queryString.split('&');
switch (query[0]) {
case "blocked":
setGlobalState(response, query[1]);
response.processAsync();
break;
case "unblock":
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Cache-Control", "no-cache", false);
response.setHeader("Content-Type", "image/png", false);
response.write("\x89PNG"); // just a broken image is enough for our purpose
var blockedResponse = getGlobalState(query[1]);
blockedResponse.setStatusLine(request.httpVersion, 200, "OK");
blockedResponse.setHeader("Cache-Control", "no-cache", false);
blockedResponse.setHeader("Content-Type", "application/javascript", false);
blockedResponse.write("window.script_executed_" + query[1] + " = true; ok(true, 'Script " + query[1] + " executed');");
blockedResponse.finish();
break;
default:
response.setStatusLine(request.httpVersion, 400, "Bad request");
break;
}
}

View File

@ -0,0 +1,5 @@
[DEFAULT]
support-files =
file_blocked_script.sjs
[test_bug1053321.html]

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<!--
This test confirms we don't block body referred sub-resources by head-referenced defer and async scripts.
If this test times out, the two image requests, that unblock the two script requests, never happen, hence
are unexpectedly blocked.
-->
<head>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!-- this script is not loaded until file_blocked_script.sjs?unblock&defer request is made,
when this script is executed, it sets window.script_executed_defer to true
-->
<script defer src="file_blocked_script.sjs?blocked&defer"></script>
<!-- this script is not loaded until file_blocked_script.sjs?unblock&async request is made,
when this script is executed, it sets window.script_executed_async to true
-->
<script async src="file_blocked_script.sjs?blocked&async"></script>
</head>
<body>
<script>
// No need for an async test, we make it all before window.onload.
//
// We can't test whether the two scripts have not been executed here, since
// preloads of the two images below (that unblock the two tested <head>
// scripts) may happen sooner than this script executes.
document.addEventListener("DOMContentLoaded", function() {
ok(window.script_executed_defer, "Deferred script executed before DOMContentLoaded");
});
window.addEventListener("load", function() {
ok(window.script_executed_async, "Async script executed before onload");
}, true);
</script>
<img src="file_blocked_script.sjs?unblock&defer"/>
<img src="file_blocked_script.sjs?unblock&async"/>
</body>

View File

@ -6,9 +6,12 @@
#include "nsHtml5TreeOpExecutor.h"
nsHtml5SpeculativeLoad::nsHtml5SpeculativeLoad()
:
#ifdef DEBUG
: mOpCode(eSpeculativeLoadUninitialized)
mOpCode(eSpeculativeLoadUninitialized),
#endif
mIsAsync(false),
mIsDefer(false)
{
MOZ_COUNT_CTOR(nsHtml5SpeculativeLoad);
}
@ -48,11 +51,11 @@ nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor)
break;
case eSpeculativeLoadScript:
aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSourceOrDocumentMode,
mCrossOrigin, mIntegrity, false);
mCrossOrigin, mIntegrity, false, mIsAsync, mIsDefer);
break;
case eSpeculativeLoadScriptFromHead:
aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSourceOrDocumentMode,
mCrossOrigin, mIntegrity, true);
mCrossOrigin, mIntegrity, true, mIsAsync, mIsDefer);
break;
case eSpeculativeLoadStyle:
aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin, mIntegrity);

View File

@ -128,7 +128,9 @@ class nsHtml5SpeculativeLoad {
nsHtml5String aType,
nsHtml5String aCrossOrigin,
nsHtml5String aIntegrity,
bool aParserInHead)
bool aParserInHead,
bool aAsync,
bool aDefer)
{
NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
"Trying to reinitialize a speculative load!");
@ -139,6 +141,8 @@ class nsHtml5SpeculativeLoad {
aType.ToString(mTypeOrCharsetSourceOrDocumentMode);
aCrossOrigin.ToString(mCrossOrigin);
aIntegrity.ToString(mIntegrity);
mIsAsync = aAsync;
mIsDefer = aDefer;
}
inline void InitStyle(nsHtml5String aUrl,
@ -221,6 +225,13 @@ class nsHtml5SpeculativeLoad {
private:
eHtml5SpeculativeLoad mOpCode;
/**
* Whether the refering element has async and/or defer attributes.
*/
bool mIsAsync;
bool mIsDefer;
nsString mUrl;
nsString mReferrerPolicy;
nsString mMetaCSP;

View File

@ -167,16 +167,20 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsHtml5String integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
bool async =
aAttributes->contains(nsHtml5AttributeName::ATTR_ASYNC);
bool defer =
aAttributes->contains(nsHtml5AttributeName::ATTR_DEFER);
mSpeculativeLoadQueue.AppendElement()->InitScript(
url,
charset,
type,
crossOrigin,
integrity,
mode == nsHtml5TreeBuilder::IN_HEAD);
mCurrentHtmlScriptIsAsyncOrDefer =
aAttributes->contains(nsHtml5AttributeName::ATTR_ASYNC) ||
aAttributes->contains(nsHtml5AttributeName::ATTR_DEFER);
mode == nsHtml5TreeBuilder::IN_HEAD,
async,
defer);
mCurrentHtmlScriptIsAsyncOrDefer = async || defer;
}
} else if (nsGkAtoms::link == aName) {
nsHtml5String rel =
@ -279,7 +283,9 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
type,
crossOrigin,
integrity,
mode == nsHtml5TreeBuilder::IN_HEAD);
mode == nsHtml5TreeBuilder::IN_HEAD,
false,
false);
}
} else if (nsGkAtoms::style == aName) {
nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();

View File

@ -947,14 +947,16 @@ nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead)
bool aScriptFromHead,
bool aAsync,
bool aDefer)
{
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin,
aIntegrity, aScriptFromHead,
aIntegrity, aScriptFromHead, aAsync, aDefer,
mSpeculationReferrerPolicy);
}

View File

@ -252,7 +252,9 @@ class nsHtml5TreeOpExecutor final : public nsHtml5DocumentBuilder,
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead);
bool aScriptFromHead,
bool aAsync,
bool aDefer);
void PreloadStyle(const nsAString& aURL, const nsAString& aCharset,
const nsAString& aCrossOrigin,