Bug 1740692 - Establish a speculative connection when receiving rel=preconnect in 103 response, r=necko-reviewers,manuel,valentin

Differential Revision: https://phabricator.services.mozilla.com/D163572
This commit is contained in:
Kershaw Chang 2022-12-18 12:44:38 +00:00
Родитель d0d17578a8
Коммит 4273a3fbb7
10 изменённых файлов: 183 добавлений и 8 удалений

Просмотреть файл

@ -11221,6 +11221,12 @@
value: false
mirror: always
# Enable `Link: rel=preconnect` in 103 Early Hint response.
- name: network.early-hints.preconnect.enabled
type: RelaxedAtomicBool
value: false
mirror: always
# Whether to use the network process or not
# Start a separate socket process. Performing networking on the socket process
# is control by a sepparate pref

Просмотреть файл

@ -0,0 +1,47 @@
/* 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/. */
#include "EarlyHintPreconnect.h"
#include "mozilla/CORSMode.h"
#include "mozilla/dom/Element.h"
#include "mozilla/StaticPrefs_network.h"
#include "nsIOService.h"
#include "nsIURI.h"
namespace mozilla::net {
void EarlyHintPreconnect::MaybePreconnect(const LinkHeader& aHeader,
nsIURI* aBaseURI,
nsIPrincipal* aPrincipal) {
if (!StaticPrefs::network_early_hints_preconnect_enabled()) {
return;
}
if (!gIOService) {
return;
}
nsCOMPtr<nsIURI> uri;
if (NS_FAILED(aHeader.NewResolveHref(getter_AddRefs(uri), aBaseURI))) {
return;
}
// only preconnect secure context urls
if (!uri->SchemeIs("https")) {
return;
}
// Note that the http connection manager will limit the number of connections
// we can make, so it should be fine we don't check duplicate preconnect
// attempts here.
CORSMode corsMode = dom::Element::StringToCORSMode(aHeader.mCrossOrigin);
if (corsMode == CORS_ANONYMOUS) {
gIOService->SpeculativeAnonymousConnect(uri, aPrincipal, nullptr);
} else {
gIOService->SpeculativeConnect(uri, aPrincipal, nullptr);
}
}
} // namespace mozilla::net

Просмотреть файл

@ -0,0 +1,24 @@
/* 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/. */
#ifndef mozilla_net_EarlyHintPreconnect_h
#define mozilla_net_EarlyHintPreconnect_h
#include "nsNetUtil.h"
namespace mozilla::net {
class EarlyHintPreconnect final {
public:
static void MaybePreconnect(const LinkHeader& aHeader, nsIURI* aBaseURI,
nsIPrincipal* aPrincipal);
EarlyHintPreconnect() = delete;
EarlyHintPreconnect(const EarlyHintPreconnect&) = delete;
EarlyHintPreconnect& operator=(const EarlyHintPreconnect&) = delete;
};
} // namespace mozilla::net
#endif // mozilla_net_EarlyHintPreconnect_h

Просмотреть файл

@ -190,10 +190,6 @@ void EarlyHintPreloader::MaybeCreateAndInsertPreload(
nsIURI* aBaseURI, nsIPrincipal* aPrincipal,
nsICookieJarSettings* aCookieJarSettings,
const nsACString& aResponseReferrerPolicy) {
if (!aLinkHeader.mRel.LowerCaseEqualsASCII("preload")) {
return;
}
nsAttrValue as;
ParseAsValue(aLinkHeader.mAs, as);

Просмотреть файл

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "EarlyHintsService.h"
#include "EarlyHintPreconnect.h"
#include "EarlyHintPreloader.h"
#include "mozilla/PreloadHashKey.h"
#include "mozilla/Telemetry.h"
@ -85,9 +86,13 @@ void EarlyHintsService::EarlyHint(const nsACString& aLinkHeader,
for (auto& linkHeader : linkHeaders) {
CollectLinkTypeTelemetry(linkHeader.mRel);
if (linkHeader.mRel.LowerCaseEqualsLiteral("preconnect")) {
EarlyHintPreconnect::MaybePreconnect(linkHeader, aBaseURI, principal);
} else if (linkHeader.mRel.LowerCaseEqualsLiteral("preload")) {
EarlyHintPreloader::MaybeCreateAndInsertPreload(
mOngoingEarlyHints, linkHeader, aBaseURI, principal, cookieJarSettings,
aReferrerPolicy);
mOngoingEarlyHints, linkHeader, aBaseURI, principal,
cookieJarSettings, aReferrerPolicy);
}
}
}

Просмотреть файл

@ -98,6 +98,7 @@ UNIFIED_SOURCES += [
"ConnectionEntry.cpp",
"ConnectionHandle.cpp",
"DnsAndConnectSocket.cpp",
"EarlyHintPreconnect.cpp",
"EarlyHintPreloader.cpp",
"EarlyHintRegistrar.cpp",
"EarlyHintsService.cpp",

Просмотреть файл

@ -2262,8 +2262,12 @@ nsresult nsHttpHandler::SpeculativeConnectInternal(
if (mDebugObservations && obsService) {
// this is basically used for test coverage of an otherwise 'hintable'
// feature
// This is used to test if the `crossOrigin` attribute is parsed correctly.
nsPrintfCString debugURL("%s%s", aURI->GetSpecOrDefault().get(),
anonymous ? "anonymous" : "use-credentials");
obsService->NotifyObservers(nullptr, "speculative-connect-request",
nullptr);
NS_ConvertUTF8toUTF16(debugURL).get());
for (auto* cp :
dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
PNeckoParent* neckoParent =

Просмотреть файл

@ -11,6 +11,7 @@ support-files =
early_hint_error.sjs
early_hint_asset.sjs
early_hint_asset_html.sjs
early_hint_preconnect_html.sjs
post.html
res.css
res.css^headers^
@ -146,3 +147,4 @@ skip-if =
support-files =
early_hint_referrer_policy_html.sjs
early_hint_preload_test_helper.jsm
[browser_103_preconnect.js]

Просмотреть файл

@ -0,0 +1,57 @@
/* 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/. */
Services.prefs.setBoolPref("network.early-hints.enabled", true);
Services.prefs.setBoolPref("network.early-hints.preconnect.enabled", true);
Services.prefs.setBoolPref("network.http.debug-observations", true);
registerCleanupFunction(function() {
Services.prefs.clearUserPref("network.early-hints.enabled");
Services.prefs.clearUserPref("network.early-hints.preconnect.enabled");
Services.prefs.clearUserPref("network.http.debug-observations");
});
// Test steps:
// 1. Load early_hint_preconnect_html.sjs
// 2. In early_hint_preconnect_html.sjs, a 103 response with
// "rel=preconnect" is returned.
// 3. We use "speculative-connect-request" topic to observe whether the
// speculative connection is attempted.
// 4. Finally, we check if the observed URL is the same as the expected.
async function test_hint_preconnect(href, crossOrigin) {
let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_preconnect_html.sjs?href=${href}&crossOrigin=${crossOrigin}`;
let observed = "";
let observer = {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
observe(aSubject, aTopic, aData) {
if (aTopic == "speculative-connect-request") {
Services.obs.removeObserver(observer, "speculative-connect-request");
observed = aData;
}
},
};
Services.obs.addObserver(observer, "speculative-connect-request");
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: requestUrl,
waitForLoad: true,
},
async function() {}
);
if (!crossOrigin) {
crossOrigin = "anonymous";
}
Assert.equal(observed, `${href}/${crossOrigin}`);
}
add_task(async function test_103_preconnect() {
await test_hint_preconnect("https://localhost", "use-credentials");
await test_hint_preconnect("https://localhost", "");
await test_hint_preconnect("https://localhost", "anonymous");
});

Просмотреть файл

@ -0,0 +1,33 @@
"use strict";
function handleRequest(request, response) {
Cu.importGlobalProperties(["URLSearchParams"]);
let qs = new URLSearchParams(request.queryString);
let href = qs.get("href");
let crossOrigin = qs.get("crossOrigin");
// write to raw socket
response.seizePower();
response.write("HTTP/1.1 103 Early Hint\r\n");
response.write(
`Link: <${href}>; rel=preconnect; crossOrigin=${crossOrigin}\r\n`
);
response.write("\r\n");
let body = `<!DOCTYPE html>
<html>
<body>
<h1>Test rel=preconnect<h1>
</body>
</html>`;
response.write("HTTP/1.1 200 OK\r\n");
response.write("Content-Type: text/html;charset=utf-8\r\n");
response.write("Cache-Control: no-cache\r\n");
response.write(`Content-Length: ${body.length}\r\n`);
response.write("\r\n");
response.write(body);
response.finish();
}